am 2e9d50a9: (-s ours) Merge "Fix Catalan keyboard layout"

* commit '2e9d50a95d5648bffcf88da1ebec13cf58ce317f':
  Fix Catalan keyboard layout
diff --git a/dictionaries/cs_wordlist.combined.gz b/dictionaries/cs_wordlist.combined.gz
index b8d4d60..d69ef64 100644
--- a/dictionaries/cs_wordlist.combined.gz
+++ b/dictionaries/cs_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/de_wordlist.combined.gz b/dictionaries/de_wordlist.combined.gz
index 8d0eb6c..f5cce9d 100644
--- a/dictionaries/de_wordlist.combined.gz
+++ b/dictionaries/de_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz
index 93c5f3d..d28ef48 100644
--- a/dictionaries/en_GB_wordlist.combined.gz
+++ b/dictionaries/en_GB_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_US_wordlist.combined.gz b/dictionaries/en_US_wordlist.combined.gz
index c2421dc..b656f88 100644
--- a/dictionaries/en_US_wordlist.combined.gz
+++ b/dictionaries/en_US_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz
index 3732993..8aa40e9 100644
--- a/dictionaries/en_wordlist.combined.gz
+++ b/dictionaries/en_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/es_wordlist.combined.gz b/dictionaries/es_wordlist.combined.gz
index e7f9125..53b8607 100644
--- a/dictionaries/es_wordlist.combined.gz
+++ b/dictionaries/es_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz
index 7de4625..0763b62 100644
--- a/dictionaries/fr_wordlist.combined.gz
+++ b/dictionaries/fr_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/hr_wordlist.combined.gz b/dictionaries/hr_wordlist.combined.gz
index 68b15c2..7694a2a 100644
--- a/dictionaries/hr_wordlist.combined.gz
+++ b/dictionaries/hr_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/it_wordlist.combined.gz b/dictionaries/it_wordlist.combined.gz
index 187e3b2..3b84cd7 100644
--- a/dictionaries/it_wordlist.combined.gz
+++ b/dictionaries/it_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/lt_wordlist.combined.gz b/dictionaries/lt_wordlist.combined.gz
index 0197616..316a5af 100644
--- a/dictionaries/lt_wordlist.combined.gz
+++ b/dictionaries/lt_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/lv_wordlist.combined.gz b/dictionaries/lv_wordlist.combined.gz
index f2338c2..b036ac2 100644
--- a/dictionaries/lv_wordlist.combined.gz
+++ b/dictionaries/lv_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/nb_wordlist.combined.gz b/dictionaries/nb_wordlist.combined.gz
index f663bbe..b6e0d42 100644
--- a/dictionaries/nb_wordlist.combined.gz
+++ b/dictionaries/nb_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/nl_wordlist.combined.gz b/dictionaries/nl_wordlist.combined.gz
index 7b4843f..48ab0f4 100644
--- a/dictionaries/nl_wordlist.combined.gz
+++ b/dictionaries/nl_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/pt_BR_wordlist.combined.gz b/dictionaries/pt_BR_wordlist.combined.gz
index 83dbe79..0dd8472 100644
--- a/dictionaries/pt_BR_wordlist.combined.gz
+++ b/dictionaries/pt_BR_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz
index 1cfab4e..1c85d66 100644
--- a/dictionaries/ru_wordlist.combined.gz
+++ b/dictionaries/ru_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/sl_wordlist.combined.gz b/dictionaries/sl_wordlist.combined.gz
index c12e7cb..41a576b 100644
--- a/dictionaries/sl_wordlist.combined.gz
+++ b/dictionaries/sl_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/sr_wordlist.combined.gz b/dictionaries/sr_wordlist.combined.gz
index bb85796..dec6ae8 100644
--- a/dictionaries/sr_wordlist.combined.gz
+++ b/dictionaries/sr_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/sv_wordlist.combined.gz b/dictionaries/sv_wordlist.combined.gz
index c107ca9..0471772 100644
--- a/dictionaries/sv_wordlist.combined.gz
+++ b/dictionaries/sv_wordlist.combined.gz
Binary files differ
diff --git a/dictionaries/tr_wordlist.combined.gz b/dictionaries/tr_wordlist.combined.gz
index b330415..fae79ca 100644
--- a/dictionaries/tr_wordlist.combined.gz
+++ b/dictionaries/tr_wordlist.combined.gz
Binary files differ
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index fb973f3..b54406f 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -51,7 +51,8 @@
             <intent-filter>
                 <action android:name="android.service.textservice.SpellCheckerService" />
             </intent-filter>
-            <meta-data android:name="android.view.textservice.scs" android:resource="@xml/spellchecker" />
+            <meta-data android:name="android.view.textservice.scs"
+                    android:resource="@xml/spellchecker" />
         </service>
 
         <activity android:name=".setup.SetupActivity"
@@ -81,21 +82,23 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name="SettingsActivity" android:label="@string/english_ime_settings"
-                  android:uiOptions="splitActionBarWhenNarrow">
+        <activity android:name=".settings.SettingsActivity"
+                android:label="@string/english_ime_settings"
+                android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
 
-        <activity android:name="com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsActivity"
+        <activity android:name=".spellcheck.SpellCheckerSettingsActivity"
                   android:label="@string/android_spell_checker_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
         </activity>
 
-        <activity android:name="DebugSettingsActivity" android:label="@string/english_ime_debug_settings">
+        <activity android:name=".settings.DebugSettingsActivity"
+                android:label="@string/english_ime_debug_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
             </intent-filter>
@@ -109,42 +112,42 @@
 
         <receiver android:name=".DictionaryPackInstallBroadcastReceiver">
             <intent-filter>
-                <action android:name="com.android.inputmethod.dictionarypack.UNKNOWN_CLIENT" />
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" />
             </intent-filter>
         </receiver>
 
         <provider android:name="com.android.inputmethod.dictionarypack.DictionaryProvider"
-                  android:grantUriPermissions="true"
-                  android:exported="false"
-                  android:authorities="@string/authority"
-                  android:multiprocess="false"
-                  android:label="@string/dictionary_provider_name">
+                android:grantUriPermissions="true"
+                android:exported="false"
+                android:authorities="@string/authority"
+                android:multiprocess="false"
+                android:label="@string/dictionary_provider_name">
         </provider>
 
         <service android:name="com.android.inputmethod.dictionarypack.DictionaryService"
-                 android:label="@string/dictionary_service_name">
+                android:label="@string/dictionary_service_name">
         </service>
 
         <receiver android:name="com.android.inputmethod.dictionarypack.EventHandler">
             <intent-filter>
                 <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
                 <action android:name="android.intent.action.DATE_CHANGED" />
-                <action android:name="com.android.inputmethod.latin.dictionarypack.UPDATE_NOW" />
+                <action android:name="com.android.inputmethod.dictionarypack.aosp.UPDATE_NOW" />
             </intent-filter>
         </receiver>
 
         <activity android:name="com.android.inputmethod.dictionarypack.DictionarySettingsActivity"
-                  android:label="@string/dictionary_settings_title"
-                  android:theme="@android:style/Theme.Holo"
-                  android:uiOptions="splitActionBarWhenNarrow">
+                android:label="@string/dictionary_settings_title"
+                android:theme="@android:style/Theme.Holo"
+                android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
         <activity android:name="com.android.inputmethod.dictionarypack.DownloadOverMeteredDialog"
-                  android:label="@string/dictionary_install_over_metered_network_prompt"
-                  android:theme="@android:style/Theme.Holo">
+                android:label="@string/dictionary_install_over_metered_network_prompt"
+                android:theme="@android:style/Theme.Holo">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
diff --git a/java/proguard.flags b/java/proguard.flags
index d65924f..c08a968 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -1,11 +1,16 @@
 # Keep classes and methods that have the @UsedForTesting annotation
 -keep @com.android.inputmethod.annotations.UsedForTesting class *
 -keepclassmembers class * {
-@com.android.inputmethod.annotations.UsedForTesting *;
+    @com.android.inputmethod.annotations.UsedForTesting *;
 }
 
 # Keep classes and methods that have the @ExternallyReferenced annotation
 -keep @com.android.inputmethod.annotations.ExternallyReferenced class *
 -keepclassmembers class * {
-@com.android.inputmethod.annotations.ExternallyReferenced *;
+    @com.android.inputmethod.annotations.ExternallyReferenced *;
+}
+
+# Keep native methods
+-keepclassmembers class * {
+    native <methods>;
 }
diff --git a/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png
index 2280243..f8df447 100644
--- a/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png
+++ b/java/res/drawable-hdpi/sym_keyboard_label_mic_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png
index d51adbe..15606e9 100644
--- a/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png
+++ b/java/res/drawable-mdpi/sym_keyboard_label_mic_holo.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png
index a7d3eaa..8eeb179 100644
--- a/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png
+++ b/java/res/drawable-xhdpi/sym_keyboard_label_mic_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
new file mode 100644
index 0000000..680421e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_active_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
new file mode 100644
index 0000000..ae26750
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
new file mode 100644
index 0000000..c92a669
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
new file mode 100644
index 0000000..40f5011
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_normal_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
new file mode 100644
index 0000000..6ff6319
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
new file mode 100644
index 0000000..818ea70
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_off_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
new file mode 100644
index 0000000..a476d2a
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_dark_pressed_on_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
new file mode 100644
index 0000000..9c280a6
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_light_normal_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
new file mode 100644
index 0000000..3c17c5e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_light_pressed_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png
new file mode 100644
index 0000000..6d2af59
--- /dev/null
+++ b/java/res/drawable-xxhdpi/btn_keyboard_key_popup_selected_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_notify_dictionary.png b/java/res/drawable-xxhdpi/ic_notify_dictionary.png
new file mode 100644
index 0000000..b61d504
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_notify_dictionary.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_subtype_keyboard.png b/java/res/drawable-xxhdpi/ic_subtype_keyboard.png
new file mode 100644
index 0000000..0bb4283
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_subtype_keyboard.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_background_holo.9.png
new file mode 100644
index 0000000..bcef0f8
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png
new file mode 100644
index 0000000..bd1ef3c
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png
new file mode 100644
index 0000000..65af4b5
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png
new file mode 100644
index 0000000..ac6750d
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_left_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png
new file mode 100644
index 0000000..cea7c05
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png
new file mode 100644
index 0000000..520fa7c
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png
new file mode 100644
index 0000000..eee2217
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_key_feedback_right_more_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
new file mode 100644
index 0000000..721c244
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_popup_panel_background_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/keyboard_suggest_strip_holo.9.png b/java/res/drawable-xxhdpi/keyboard_suggest_strip_holo.9.png
new file mode 100644
index 0000000..08176fe
--- /dev/null
+++ b/java/res/drawable-xxhdpi/keyboard_suggest_strip_holo.9.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/suggestions_strip_divider.png b/java/res/drawable-xxhdpi/suggestions_strip_divider.png
new file mode 100644
index 0000000..d13ca42
--- /dev/null
+++ b/java/res/drawable-xxhdpi/suggestions_strip_divider.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
new file mode 100644
index 0000000..be3cb7c
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_delete_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png
new file mode 100644
index 0000000..b6d4477
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_label_mic_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png b/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png
new file mode 100644
index 0000000..7cd0684
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_language_switch.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png
new file mode 100644
index 0000000..7d95807
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_return_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png
new file mode 100644
index 0000000..6b09d8e
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_search_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png
new file mode 100644
index 0000000..7041bb6
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_settings_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png
new file mode 100644
index 0000000..2b4fbbb
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png
new file mode 100644
index 0000000..91c8603
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_shift_locked_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png
new file mode 100644
index 0000000..65aa5ea
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_space_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png
new file mode 100644
index 0000000..1f4ae3d
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_tab_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png
new file mode 100644
index 0000000..f04cadf
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png
new file mode 100644
index 0000000..e74d523
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_voice_off_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png
new file mode 100644
index 0000000..85289b2
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_zwj_holo.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png b/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png
new file mode 100644
index 0000000..e610678
--- /dev/null
+++ b/java/res/drawable-xxhdpi/sym_keyboard_zwnj_holo.png
Binary files differ
diff --git a/java/res/layout/research_feedback_fragment_layout.xml b/java/res/layout/research_feedback_fragment_layout.xml
index 2915a77..505a1e8 100644
--- a/java/res/layout/research_feedback_fragment_layout.xml
+++ b/java/res/layout/research_feedback_fragment_layout.xml
@@ -18,11 +18,10 @@
    want a dialog, but it must be its own activity so we can launch the soft
    keyboard on it.  A regular dialog will not work since it would be launched from
    the IME. -->
-<ScrollView>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android">
     <LinearLayout
-         xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
-         android:layout_height="match_parent"
+         android:layout_height="wrap_content"
          android:layout_marginStart="8dip"
          android:layout_marginEnd="8dip"
          android:orientation="vertical">
diff --git a/java/res/layout/setup_welcome_video.xml b/java/res/layout/setup_welcome_video.xml
index 7517732..01c25ea 100644
--- a/java/res/layout/setup_welcome_video.xml
+++ b/java/res/layout/setup_welcome_video.xml
@@ -36,13 +36,15 @@
                 android:id="@+id/setup_welcome_video"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:background="@color/setup_background" />
+                android:background="@color/setup_background"
+                android:contentDescription="@string/setup_welcome_additional_description"/>
             <ImageView
                 android:id="@+id/setup_welcome_image"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:adjustViewBounds="true"
-                android:visibility="gone" />
+                android:visibility="gone"
+                android:contentDescription="@string/setup_welcome_additional_description"/>
         </LinearLayout>
         <View
             android:layout_weight="@integer/setup_welcome_video_end_padding_weight_in_screen"
diff --git a/java/res/raw/main_de.dict b/java/res/raw/main_de.dict
index a59f782..5d35e64 100644
--- a/java/res/raw/main_de.dict
+++ b/java/res/raw/main_de.dict
Binary files differ
diff --git a/java/res/raw/main_en.dict b/java/res/raw/main_en.dict
index 086874d..6564d47 100644
--- a/java/res/raw/main_en.dict
+++ b/java/res/raw/main_en.dict
Binary files differ
diff --git a/java/res/raw/main_es.dict b/java/res/raw/main_es.dict
index ac15d39..f5906c2 100644
--- a/java/res/raw/main_es.dict
+++ b/java/res/raw/main_es.dict
Binary files differ
diff --git a/java/res/raw/main_fr.dict b/java/res/raw/main_fr.dict
index 9044c7e..31fb2af 100644
--- a/java/res/raw/main_fr.dict
+++ b/java/res/raw/main_fr.dict
Binary files differ
diff --git a/java/res/raw/main_it.dict b/java/res/raw/main_it.dict
index e289cef..523f645 100644
--- a/java/res/raw/main_it.dict
+++ b/java/res/raw/main_it.dict
Binary files differ
diff --git a/java/res/raw/main_pt_br.dict b/java/res/raw/main_pt_br.dict
index 8c14499..557d46e 100644
--- a/java/res/raw/main_pt_br.dict
+++ b/java/res/raw/main_pt_br.dict
Binary files differ
diff --git a/java/res/raw/main_ru.dict b/java/res/raw/main_ru.dict
index 7074416..86c368e 100644
--- a/java/res/raw/main_ru.dict
+++ b/java/res/raw/main_ru.dict
Binary files differ
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-af/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-af/strings-appname.xml
index f65d45b..c481690 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-af/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-sleutelbord (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-speltoetser (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android-sleutelbordinstellings (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android-speltoetserinstellings (AOSP)"</string>
 </resources>
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 2892940..2919b9b 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Geen wagperiode nie"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Verstek"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms."</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Stelsel se verstek"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Stel kontakname voor"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Gebruik name van kontakte vir voorstelle en korreksies"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dubbelspasie-punt"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dubbeltik op spasiebalk voeg \'n punt in, gevolg deur \'n spasie"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Outohoofletters"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Die eerste woord van elke sin moet met \'n hoofletter begin"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Persoonlike woordeboek"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Voeg woordeboeke by"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hoofwoordeboek"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Wys voorstelle vir korrigering"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Wys altyd"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Wys in portretmodus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Versteek altyd"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokkeer aanstootlike woorde"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Moenie potensieel aanstootlike woorde voorstel nie"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Outokorrigering"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spasiebalk en leestekens korrigeer outomaties woorde wat verkeerd gespel is"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Af"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Moet hierdie lêer regtig vir <xliff:g id="LOCALE_NAME">%s</xliff:g> geïnstalleer word?"</string>
     <string name="error" msgid="8940763624668513648">"Daar was \'n fout"</string>
     <string name="button_default" msgid="3988017840431881491">"Verstek"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Taal en invoer"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Kies invoermetode"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Welkom by <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"met Gebaar-tik"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Kom aan die gang"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Volgende stap"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Stel <xliff:g id="APPLICATION_NAME">%s</xliff:g> op"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Aktiveer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Verifieer asseblief \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" in jou Taal- en invoerinstellings. Dit sal dit magtig om op jou toestel te loop."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> is reeds geaktiveer in jou Taal- en invoer-instellings - hierdie stap is dus klaar. Aan na die volgende een!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktiveer in instellings"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Skakel oor na <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Volgende, kies \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" as jou aktiewe teks-invoermetode."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Wissel invoermetodes"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Veels geluk, jy\'s gereed!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nou kan jy in al jou gunstelingprogramme tik met <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Stel bykomende tale op"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Klaar"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Wys program-ikoon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Wys program-ikoon in die lanseerpoort"</string>
     <string name="app_name" msgid="6320102637491234792">"Woordeboekverskaffer"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Woordeboekverskaffer"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Woordeboek-diens"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"\'n Woordeboek is vir <xliff:g id="LANGUAGE">%1$s</xliff:g> beskikbaar"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Druk om te hersien en af te laai"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Laai tans af: voorstelle vir <xliff:g id="LANGUAGE">%1$s</xliff:g> sal binnekort gereed wees."</string>
+    <string name="version_text" msgid="2715354215568469385">"Weergawe <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Voeg by"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Voeg by woordeboek"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Nog opsies"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Minder opsies"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Woord:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Kortpad:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Taal:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Tik \'n woord in"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Opsionele kortpad"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Redigeer woord"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Redigeer"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Vee uit"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Jy het geen woorde in die gebruikerwoordeboek nie. Voeg \'n woord by deur die Byvoegknoppie (+) te raak."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Vir alle tale"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Nog tale…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Vee uit"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-am/dictionary-pack.xml b/java/res/values-am/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-am/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-am/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-am/strings-appname.xml
index f65d45b..aee2e24 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-am/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"የAndroid ቁልፍ ሰሌዳ (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android ፊደል አራሚ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"የAndroid ቁልፍ ሰሌዳ ቅንብሮች (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"የAndroid ፊደል አራሚ ቅንብሮች (AOSP)"</string>
 </resources>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 03a8b93..65382de 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"የዘገየ የለም"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ነባሪ"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ሚሊሰከንድ"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"የስርዓት ነባሪ"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"የዕውቂያ ስም ጠቁም"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ከዕውቂያዎች ለጥቆማዎች እና ማስተካከያዎች ስሞች ተጠቀም"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"የድርብ-ክፍተት ነጥብ"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"የክፍተት አሞሌው ላይ ሁለቴ መታ ማድረግ አንድ ነጥብ እና ክፍተት አስከትሎ ያስገባል"</string>
     <string name="auto_cap" msgid="1719746674854628252">"ራስ-ሰር አቢይ ማድረግ"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"የእያንዳንዱ ዓረፍተ ነገር የመጀመሪያ ቃል በአቢይ ሆሄ ያስቀምጡ"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"የግል መዝገበ-ቃላት"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"መዝገበ ቃላቶች ጨምር"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"ዋና መዝገበ ቃላት"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"የማስተካከያ ጥቆማዎች አሳይ"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ሁልጊዜ አሳይ"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"በቁም አቀማመጥ ሁነታ አሳይ"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ሁልጊዜ ደብቅ"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"አፀያፊ ቃላትን አግድ"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"አጸያፊ ሊሆኑ የሚችሉ ቃላትን አትጠቁም"</string>
     <string name="auto_correction" msgid="7630720885194996950">"በራስ-ማስተካከል"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"የቦታ ቁልፍ እና ሥርዓተ ነጥብ በስህተት የተተየቡ ቃላትን  በራስሰር ያስተካክላሉ ።"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ውጪ"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"እውን ይሄ ፋይል ለ<xliff:g id="LOCALE_NAME">%s</xliff:g> ይጫን?"</string>
     <string name="error" msgid="8940763624668513648">"ስህተት ተከስቶ ነበር"</string>
     <string name="button_default" msgid="3988017840431881491">"ነባሪ"</string>
-    <string name="language_settings" msgid="1671153053201809031">"ቋንቋ እና ግቤት"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"የግቤት ስልት ይምረጡ"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"እንኳን ወደ <xliff:g id="APPLICATION_NAME">%s</xliff:g> በደህና መጡ"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"በጣት ምልክት መተየብ"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"ጀምር"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"ቀጣይ ደረጃ"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>ን በማዋቀር ላይ"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>ን ያንቁ"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"እባክዎ «<xliff:g id="APPLICATION_NAME">%s</xliff:g>»ን በቋንቋ እና ግቤት ቅንብሮችዎ ውስጥ ያረጋግጡት። ይሄ እሱ በመሣሪያዎ ላይ እንዲሄድ ይፈቅድለታል።"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> አስቀድሞ በእርስዎ ቋንቋ እና ግቤት ቅንብሮች ውስጥ ነቅቷል፣ ስለዚህ ይህ ደረጃ ተከናውኗል። ቀጣዩ ላይ!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"በቅንብሮች ውስጥ ያንቁ"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"ወደ <xliff:g id="APPLICATION_NAME">%s</xliff:g> ይቀይሩ"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"በመቀጠል «<xliff:g id="APPLICATION_NAME">%s</xliff:g>»ን እንደ የጽሑፍ ግቤት ስልትዎ ይምረጡት።"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"የግቤት ስልቶችን ቀያይር"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"እንኳን ደስ አለዎት፣ በቃ ጨርሰዋል!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"አሁን በሁሉም ተወዳጅ መተግበሪያዎችዎ ላይ በ<xliff:g id="APPLICATION_NAME">%s</xliff:g> መተየብ ይችላሉ።"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"ተጨማሪ ቋንቋዎችን ያዋቅሩ"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"ጨርሷል"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"የመተግበሪያ አዶ አሳይ"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"የመተግበሪያ አዶውን በማስጀመሪያው ውስጥ አሳይ"</string>
     <string name="app_name" msgid="6320102637491234792">"የመዝገበ-ቃላት አቅራቢ"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"የመዝገበ-ቃላት አቅራቢ"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"የመዝገበ-ቃላት አገልግሎት"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"መዝገበ-ቃላት ለ<xliff:g id="LANGUAGE">%1$s</xliff:g> ይገኛል"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"ለመገምገምና ለማውረድ ይጫኑ"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"በማውረድ ላይ፦ የ<xliff:g id="LANGUAGE">%1$s</xliff:g> ጥቆማ አስተያየቶች በቅርቡ ዝግጁ ይሆናሉ።"</string>
+    <string name="version_text" msgid="2715354215568469385">"ሥሪት <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"አክል"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"ወደ መዝገበ-ቃላት አክል"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ሐረግ"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"ተጨማሪ አማራጮች"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ያነሱ አማራጮች"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"እሺ"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"ቃል፦"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"አቋራጭ፦"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ቋንቋ፦"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"አንድ ቃል ይተይቡ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"አማራጭ አቋራጭ"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"ቃሉን አርትዕ"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"አርትዕ"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"ሰርዝ"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"በተጠቃሚ መዝገበ-ቃላት ውስጥ ምንም ቃላቶች የሉዎትም። የአክል (+) አዝራሩን በመንካት ቃል ማከል ይችላሉ።"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"ለሁሉም ቋንቋዎች"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"ተጨማሪ ቋንቋዎች…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"ሰርዝ"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ሀለሐመሠረሰሸቀበቨተቸኀነኘአከኸወዐዘዠየደጀገጠጨጰጸፀፈፐ"</string>
 </resources>
diff --git a/java/res/values-ar/dictionary-pack.xml b/java/res/values-ar/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ar/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-ar/strings-appname.xml b/java/res/values-ar/strings-appname.xml
new file mode 100644
index 0000000..d5176d0
--- /dev/null
+++ b/java/res/values-ar/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"لوحة مفاتيح Android ‏(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"المدقق الإملائي في Android‏ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"إعدادات لوحة مفاتيح Android‏ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"إعدادات المدقق الإملائي في Android‏ (AOSP)"</string>
+</resources>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index c16f224..6f18c94 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بلا تأخير"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"افتراضي"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> مللي ثانية"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"الإعداد الافتراضي للنظام"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"اقتراح أسماء جهات الاتصال"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"استخدام الأسماء من جهات الاتصال للاقتراحات والتصحيحات"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"نقطة المسافة المزدوجة"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"يؤدي النقر نقرًا مزدوجًا على مفتاح المسافة إلى إدخال نقطة متبوعة بمسافة"</string>
     <string name="auto_cap" msgid="1719746674854628252">"أحرف كبيرة تلقائيًا"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"كتابة الحرف الأول من كل جملة بحرف كبير."</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"القاموس الشخصي"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"القواميس الإضافية"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"القاموس الرئيسي"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"عرض اقتراحات التصحيح"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"عرض دومًا"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"العرض في وضع رأسي"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"إخفاء دومًا"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"حظر الكلمات المسيئة"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"عدم اقتراح كلمات محتمل أن تكون مسيئة"</string>
     <string name="auto_correction" msgid="7630720885194996950">"التصحيح التلقائي"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"تؤدي المسافة والترقيم إلى تصحيح الكلمات المكتوبة بشكل غير صحيح"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"إيقاف"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"هل تريد حقًا تثبيت هذا الملف للغة <xliff:g id="LOCALE_NAME">%s</xliff:g>؟"</string>
     <string name="error" msgid="8940763624668513648">"حدث خطأ"</string>
     <string name="button_default" msgid="3988017840431881491">"الافتراضية"</string>
-    <string name="language_settings" msgid="1671153053201809031">"اللغة والإدخال"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"اختيار أسلوب الإدخال"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"مرحبا بكم في <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"مع الكتابة بالإشارة"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"بدء الاستخدام"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"الخطوة التالية"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"إعداد <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"تمكين <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"يُرجى تحديد \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" في إعدادات اللغة والإدخال حيث يسمح هذا الإعداد بتشغيله على جهازك."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"تم تمكين <xliff:g id="APPLICATION_NAME">%s</xliff:g> من قبل في إعدادات اللغة والإدخال، وبالتالي هذه الخطوة مكتملة. انتقل إلى الخطوة التالية."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"تمكين في الإعدادات"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"التبديل إلى <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"بعد ذلك، حدد \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" باعتباره أسلوب إدخال النص النشط."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"تبديل أساليب الإدخال"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"تهانينا، بهذا تكون قد انتهيت من الإعداد"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"الآن أصبح بإمكانك الكتابة في جميع تطبيقاتك المفضلة باستخدام <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"تهيئة اللغات الإضافية"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"إنهاء العملية"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"عرض رمز التطبيق"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"عرض رمز التطبيق في المشغل"</string>
     <string name="app_name" msgid="6320102637491234792">"مقدم القاموس"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"مقدم القاموس"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"خدمة القاموس"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"هناك قاموس متوفر للغة <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"اضغط للمراجعة والتنزيل"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"جارٍ التنزيل: ستتوفر اقتراحات للغة <xliff:g id="LANGUAGE">%1$s</xliff:g> بعد قليل."</string>
+    <string name="version_text" msgid="2715354215568469385">"الإصدار <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"إضافة"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"إضافة إلى القاموس"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"عبارة"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"خيارات أكثر"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"خيارات أقل"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"موافق"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"الكلمة:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"الاختصار:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"اللغة:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"اكتب كلمة"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"اختصار اختياري"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"تعديل كلمة"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"تعديل"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"حذف"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"ليست لديك أية كلمات في قاموس المستخدم. يمكنك إضافة كلمة من خلال لمس الزر \"إضافة\" (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"لجميع اللغات"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"مزيد من اللغات..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"حذف"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" أ ب ت ث ج ح خ د ذ ر ز س ش ص ض ط ظ ع غ ف ق ك ل م ن ه و ي"</string>
 </resources>
diff --git a/java/res/values-be/dictionary-pack.xml b/java/res/values-be/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-be/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-be/strings-appname.xml b/java/res/values-be/strings-appname.xml
new file mode 100644
index 0000000..2f9593b
--- /dev/null
+++ b/java/res/values-be/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Клавіятура Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Iнструмент праверкi правапiсу для Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Налады клавіятуры Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Налады інструмента праверкі правапісу для Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index eca24eb..e5db3f6 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,15 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Няма затрымкі"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Па змаўчанні"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> мс"</string>
+    <!-- no translation found for settings_system_default (6268225104743331821) -->
+    <skip />
     <string name="use_contacts_dict" msgid="4435317977804180815">"Прапан. імёны кантактаў"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Выкарыстоўваць імёны са спісу кантактаў для прапаноў і выпраўл."</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Падвойны iнтэрвал"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Падвойнае нацiсканне на прабел ўстаўляе iнтэрвал з наступным прабелам"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Аўтаматычна рабіць вялікія літары"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Пісаць з загалоўнай літары першае слова ў кожным сказе"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Персанальны слоўнік"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Дадатковыя слоўнікі"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Асноўны слоўнік"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Паказаць прапановы на выпраўленне"</string>
@@ -60,6 +59,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Заўсёды паказваць"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Паказаць у партрэтным рэжыме"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Заўседы хаваць"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Блакіраваць абразлівыя словы"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Не прапануйце патэнцыяльна абразлівых слоў"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Аўтавыпраўленне"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Прабелы і пунктуацыйныя знакі дазваляюць аўтаматычна выпраўляць памылкова ўведзеныя словы"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Адключаны"</string>
@@ -171,8 +172,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Сапраўды ўсталяваць гэты файл на мове: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Была памылка"</string>
     <string name="button_default" msgid="3988017840431881491">"Па змаўчанні"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Мова і ўвод"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Выберыце метад уводу"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Вітаем у прыкладанні <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"з уводам жэстамі"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Пачаць"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Далей"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Наладка прыкладання <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Уключыць прыкладанне <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Праверце прыкладанне \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" на сваёй мове і параметры ўводу. Гэта дасць магчымасць дазволіць яму працаваць на вашай прыладзе."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Прыкладанне <xliff:g id="APPLICATION_NAME">%s</xliff:g> ужо ўключана для вашай мовы і параметраў уводу, так што гэты крок зроблены. Пераходзім да наступнага!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Уключыць у наладах"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Пераключыцца на прыкладанне <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Выберыце \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" як актыўны метад уводу тэксту."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Пераключэнне метадаў уводу"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Усё гатова!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Цяпер вы можаце ўводзіць ўсе свае любімыя прыкладанні з iмем <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Наладка дадатковых моў"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Гатова"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Паказаць значок прыкладання"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Паказаць значок прыкладання ў панэлi запуску"</string>
     <string name="app_name" msgid="6320102637491234792">"Пастаўшчык слоўніка"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Пастаўшчык слоўніка"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Слоўнік"</string>
@@ -203,4 +220,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Слоўнік для мовы \"<xliff:g id="LANGUAGE">%1$s</xliff:g>\""</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Нацiснiце, каб прагледзець i спампаваць"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Загрузка: прапановы для мовы \"<xliff:g id="LANGUAGE">%1$s</xliff:g>\" хутка з\'явяцца."</string>
+    <string name="version_text" msgid="2715354215568469385">"Версія <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Дадаць"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Дадаць у слоўнік"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Выраз"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Дадатковыя параметры"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Асн. параметры"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OК"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Слова:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Шлях хуткага доступу:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Мова:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Увядзіце слова"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Дадатковы цэтлiк"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Рэдагаваць слова"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Рэдагаваць"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Выдаліць"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"У вашым карыстальніцкім слоўніку няма ніводнага слова. Вы можаце дадаваць словы, дакранаючыся да кнопкі \"+\" у пункце меню \"Дадаць\"."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Для ўсіх моў"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Іншыя мовы..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Выдаліць"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-bg/dictionary-pack.xml b/java/res/values-bg/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-bg/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-bg/strings-appname.xml b/java/res/values-bg/strings-appname.xml
new file mode 100644
index 0000000..f82163e
--- /dev/null
+++ b/java/res/values-bg/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Клавиатура на Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Програма за правописна проверка за Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Настройки на клавиатурата на Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Настройки на програмата за правописна проверка за Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 3a9b579..89665cd 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задържане"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По подразбиране"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> милисек"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Станд. за системата"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Предложения за контакти"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Използване на имена от „Контакти“ за предложения и поправки"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Точка чрез двоен интервал"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Двукр. докосване на клав. за интервал вмъква точка, следвана от интервал"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Автоматично поставяне на главни букви"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Правене на първата дума от всяко изречение главна"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Личен речник"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Добавени речници"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основен речник"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Показване на предложения за поправка"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Винаги да се показва"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Показване във вертикална ориентация"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Винаги да се скрива"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Блокиране на обидни думи"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Без потенциално обидни думи"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Автоматична поправка"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Клавишът за интервал и пунктуация авт. поправя сгрешени думи"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Изкл."</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Наистина ли да се инсталира този файл за <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Възникна грешка"</string>
     <string name="button_default" msgid="3988017840431881491">"Стандартни"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Език и въвеждане"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Избор на метод на въвеждане"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Добре дошли в/ъв <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"с въвеждане чрез жест"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Първи стъпки"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Следваща стъпка"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Настройване на <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Активирайте <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Поставете отметка за <xliff:g id="APPLICATION_NAME">%s</xliff:g> в „Език и въвеждане“. Така ще упълномощите приложението да се изпълнява на устройството."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Вече активирахте <xliff:g id="APPLICATION_NAME">%s</xliff:g> в настройките си за език и въвеждане, така че тази стъпка е изпълнена. Преминете към следващата!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Активиране в настройките"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Превключете към <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"След това изберете <xliff:g id="APPLICATION_NAME">%s</xliff:g> като активен метод на въвеждане на текст."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Превключване на методите на въвеждане"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Поздравления! Вече сте готови!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Вече можете да пишете във всичките си любими приложения посредством <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Конфигуриране на допълнителни езици"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Край"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Показв. на иконата на прилож."</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Показване на иконата на приложението в стартовия панел"</string>
     <string name="app_name" msgid="6320102637491234792">"Доставчик на речника"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Доставчик на речника"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Услуга за речник"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"За <xliff:g id="LANGUAGE">%1$s</xliff:g> е налице речник"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Натиснете, за да прегледате и изтеглите"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Изтегля се: Предложенията за <xliff:g id="LANGUAGE">%1$s</xliff:g> ще бъдат готови скоро."</string>
+    <string name="version_text" msgid="2715354215568469385">"Версия <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Добавяне"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Добавяне в речника"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Фраза"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Още опции"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"По-малко опции"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Дума:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Пряк път:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Език:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Напишете дума"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Незадължителен пряк път"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Редактиране на дума"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Редактиране"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Изтриване"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Нямате думи в потребителския речник. Добавете, като докоснете бутона за добавяне (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"За всички езици"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Още езици…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Изтриване"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЪЮЯ"</string>
 </resources>
diff --git a/java/res/values-ca/dictionary-pack.xml b/java/res/values-ca/strings-appname.xml
similarity index 62%
rename from java/res/values-ca/dictionary-pack.xml
rename to java/res/values-ca/strings-appname.xml
index f65d45b..4abf513 100644
--- a/java/res/values-ca/dictionary-pack.xml
+++ b/java/res/values-ca/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclat d\'Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Corrector ortogràfic d\'Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Configuració del teclat d\'Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Configuració del corrector ortogràfic d\'Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 3670a98..18cff92 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sense retard"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminat"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Predeterm. del sist."</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggereix noms de contactes"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilitza els noms de contactes per fer suggeriments i correccions"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punt amb doble espai"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Picar dues vegades la barra d\'espai insereix punt i espai blanc"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majúscules automàtiques"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Posa en majúscula la primera paraula de cada frase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Diccionari personal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionaris complementaris"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionari principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Suggeriments de correcció"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostra en mode vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Amaga sempre"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloqueja paraules ofensives"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"No suggereixis paraules potencialment ofensives"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correcció automàtica"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Prémer tecla d\'espai o punt. per corregir errors"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactiva"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Realment vols instal·lar aquest fitxer per a <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"S\'ha produït un error"</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminat"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Idioma i introducció de text"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Selecció de mètodes d\'introducció"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Et donem la benvinguda a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"amb Escriptura gestual"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Comença"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Pas següent"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"S\'està configurant <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Activa <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Selecciona \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" a la configuració d\'Idioma i introducció de text perquè es pugui executar al teu dispositiu."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"L\'aplicació <xliff:g id="APPLICATION_NAME">%s</xliff:g> ja està activada per a la teva Configuració d\'idioma i d\'introducció de text. Pots passar al següent."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Activa a la configuració"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Canvi a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"A continuació, selecciona \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" com a mètode d\'introducció de text actiu."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Canvia els mètodes d\'introducció"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Enhorabona, ja has acabat!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Ara ja pots escriure en totes les teves aplicacions preferides amb <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configura altres idiomes"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Finalitzat"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostra la icona de l\'aplicació"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostra la icona de l\'aplicació al menú d\'aplicacions"</string>
     <string name="app_name" msgid="6320102637491234792">"Proveïdor de diccionaris"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Proveïdor de diccionaris"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Servei de diccionari"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Hi ha un diccionari disponible per a l\'idioma: <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Prem per revisar-lo i per baixar-lo"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Baixada: els suggeriments per a <xliff:g id="LANGUAGE">%1$s</xliff:g> estaran disponibles ben aviat."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versió <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Afegeix"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Afegeix al diccionari"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Més opcions"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menys opcions"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"D\'acord"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Paraula:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Drecera:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Escriu una paraula"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Drecera opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edició de la paraula"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edita"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Suprimeix"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"No tens cap paraula al diccionari de l\'usuari. Per afegir una paraula, toca el botó Afegeix (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Per a tots els idiomes"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Més idiomes..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Suprimeix"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-cs/dictionary-pack.xml b/java/res/values-cs/strings-appname.xml
similarity index 63%
rename from java/res/values-cs/dictionary-pack.xml
rename to java/res/values-cs/strings-appname.xml
index f65d45b..bea9740 100644
--- a/java/res/values-cs/dictionary-pack.xml
+++ b/java/res/values-cs/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Klávesnice Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Kontrola pravopisu Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Nastavení klávesnice Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Nastavení kontroly pravopisu Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index bd64761..9b20f7c 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez prodlevy"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Výchozí"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Výchozí nastavení"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Navrhovat jména kontaktů"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Použít jména ze seznamu kontaktů k návrhům a opravám"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Tečka dvojitým mezerníkem"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dvojím klepnutím na mezerník vložíte tečku následovanou mezerou."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Velká písmena automaticky"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Kapitalizace prvního slova každé věty"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Osobní slovník"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Doplňkové slovníky"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hlavní slovník"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Zobrazit návrhy oprav"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovat"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Zobrazovat v režimu na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývat"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokovat nevhodná slova"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Nenavrhovat potenciálně nevhodná slova"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatické opravy"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stisknutím mezerníku a interpunkce se automaticky opravují chybně napsaná slova"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuto"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Chcete nainstalovat tento soubor pro jazyk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Došlo k chybě"</string>
     <string name="button_default" msgid="3988017840431881491">"Výchozí"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Jazyk a zadávání"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Výběr metody zadávání dat"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Vítá vás <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"s psaním gesty"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Začínáme"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Další krok"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Nastavení aplikace <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Zapnutí aplikace <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"V nastavení Jazyk a zadávání zaškrtněte aplikaci <xliff:g id="APPLICATION_NAME">%s</xliff:g>, povolíte tak její spuštění."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Aplikace <xliff:g id="APPLICATION_NAME">%s</xliff:g> je již v nastaveních jazyka a vstupu zapnuta, a tento krok je proto již proveden. Pokračujme dalším."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktivovat v nastavení"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Přepnutí na aplikaci <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Poté vyberte jako aktivní metodu zadávání textu možnost <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Přepnout metody zadávání"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gratulujeme, vše je připraveno."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nyní můžete ve všech svých oblíbených aplikacích psát pomocí aplikace <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Nakonfigurovat další jazyky"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Hotovo"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Zobrazit ikonu aplikace"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Zobrazí ikonu aplikace ve spouštěči"</string>
     <string name="app_name" msgid="6320102637491234792">"Poskytovatel slovníku"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Poskytovatel slovníku"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Služba slovníku"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Je k dispozici slovník pro jazyk <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Stisknutím zkontrolujete a stáhnete"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Stahování: návrhy pro jazyk <xliff:g id="LANGUAGE">%1$s</xliff:g> budou brzy k dispozici."</string>
+    <string name="version_text" msgid="2715354215568469385">"Verze <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Přidat"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Přidat do slovníku"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fráze"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Více možností"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Méně možností"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Slovo:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Zkratka:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Jazyk:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Napište slovo."</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Volitelná zkratka"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Upravit slovo"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Upravit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Smazat"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"V uživatelském slovníku nejsou žádná slova. Slovo můžete přidat stisknutím tlačítka Přidat (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Pro všechny jazyky"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Další jazyky…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Smazat"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCČDEFGHChIJKLMNOPQRŘSŠTUVWXYZŽ"</string>
 </resources>
diff --git a/java/res/values-da/dictionary-pack.xml b/java/res/values-da/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-da/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-da/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-da/strings-appname.xml
index f65d45b..4db49c7 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-da/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-tastatur (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-stavekontrol (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Indstillinger for Android-tastatur (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Indstillinger for Android-stavekontrol (AOSP)"</string>
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 5642917..f4f7d85 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Ingen forsink."</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Systemstandard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Foreslå navne på kontakter"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Brug navne fra Kontaktpersoner til forslag og rettelser"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"To mellemrum for punktum"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"To tryk på mellemrumstasten indsætter et punktum og et mellemrum"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Skriv aut. med stort"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Skriv det første ord i hver sætning med stort"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personlig ordbog"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tillægsordbøger"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hovedordbog"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Vis rettelsesforslag"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis altid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Vis i portræt"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul altid"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloker stødende ord"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Foreslå ikke potentielt stødende ord"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatisk rettelse"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellemrumstast og tegnsætning retter automatisk forkerte ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Fra"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Er du klar til at installere denne fil til <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Der opstod en fejl"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Sprog og input"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Vælg inputmetode"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Velkommen til <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"med Berøringsinput"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Kom godt i gang"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Næste trin"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> konfigureres"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Aktivér <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Markér \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" i Sprog og inputindstillinger. Dermed får appen tilladelse til at køre på din enhed."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> er allerede aktiveret i indstillingerne for dit sprog og dine input, så dette skridt er udført. Videre til det næste!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktivér i Indstillinger"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Skift til <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Dernæst skal du vælge \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" som din aktive sms-indtastningsmetode."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Skift indtastningsmetode"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Så er du klar."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nu kan du skrive i alle dine favoritapps med <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurer flere sprog"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Afslut"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Vis appikon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Vis appikon på applikationsliste"</string>
     <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Ordbogstjeneste"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Der er en tilgængelig ordbog for <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Tryk for at gennemgå og downloade"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Downloader: Der vil snart være forslag klar på <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Tilføj"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Føj til ordbog"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Sætning"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Flere muligheder"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Færre muligh."</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Ord:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Genvej:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Sprog:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Skriv et ord"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valgfri genvej"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Rediger ord"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Rediger"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Slet"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Du har ikke nogen ord i brugerordbogen. Du kan tilføje et ord ved at trykke på knappen Tilføj (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"For alle sprog"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Flere sprog..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Slet"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-de/dictionary-pack.xml b/java/res/values-de/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-de/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-de/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-de/strings-appname.xml
index f65d45b..2073f55 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-de/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-Tastatur (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-Rechtschreibprüfung (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android-Tastatureinstellungen (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Einstellungen für die Android-Rechtschreibprüfung (AOSP)"</string>
 </resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 1c9bec2..ca66b77 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Keine Verzögerung"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Systemstandardeinstellung"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Kontakte vorschlagen"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Namen aus \"Kontakte\" als Vorschläge und Korrekturmöglichkeiten anzeigen"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punkt plus Leerzeichen"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Für Punkt plus Leerzeichen zweimal auf die Leertaste tippen"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Autom. Groß-/Kleinschreibung"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Das erste Wort jedes Satzes großschreiben"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Mein Wörterbuch"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Erweiterte Wörterbücher"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Allgemeines Wörterbuch"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Änderungsvorschläge"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Immer anzeigen"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Im Hochformat anzeigen"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Nie anzeigen"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Anstößige Wörter sperren"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Keine potenziell anstößigen Wörter vervollständigen"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autokorrektur"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Korrektur fehlerhafter Wörter durch Leertaste und Satzzeichen"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Aus"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Möchten Sie diese Datei für <xliff:g id="LOCALE_NAME">%s</xliff:g> installieren?"</string>
     <string name="error" msgid="8940763624668513648">"Es ist ein Fehler aufgetreten"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Sprache &amp; Eingabe"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Eingabemethode wählen"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Willkommen bei <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"mit Bewegungseingabe"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Jetzt starten"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Nächster Schritt"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> einrichten"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> aktivieren"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Aktivieren Sie \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" unter \"Sprache &amp; Eingabe\". Damit wird die App auf Ihrem Gerät autorisiert."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> ist bereits in Ihren Sprach- und Eingabeeinstellungen aktiviert. Fahren Sie mit dem nächsten Schritt fort."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"In den Einstellungen aktivieren"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Zu <xliff:g id="APPLICATION_NAME">%s</xliff:g> wechseln"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Wählen Sie dann \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" als Ihre aktive Texteingabemethode."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Eingabemethode wechseln"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Die Einrichtung ist abgeschlossen"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Jetzt können Sie in allen Ihren Lieblings-Apps über <xliff:g id="APPLICATION_NAME">%s</xliff:g> Text eingeben."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Weitere Sprachen konfigurieren"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Fertig"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"App-Symbol anzeigen"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"App-Symbol in der Übersicht anzeigen"</string>
     <string name="app_name" msgid="6320102637491234792">"Wörterbuchbereitstellung"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Wörterbuchbereitstellung"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Wörterbuch"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Es ist ein Wörterbuch für <xliff:g id="LANGUAGE">%1$s</xliff:g> verfügbar."</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Zum Lesen und Herunterladen drücken"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Download wurde gestartet: Vorschläge für <xliff:g id="LANGUAGE">%1$s</xliff:g> sind in Kürze bereit."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Hinzufügen"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Zum Wörterbuch hinzufügen"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Wortgruppe"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Weitere Optionen"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Weniger Optionen"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Wort:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Tastaturkürzel:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Sprache:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Wort eingeben"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Optionales Tastaturkürzel"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Wort bearbeiten"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Bearbeiten"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Löschen"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Es sind noch keine Wörter in Ihrem Wörterbuch vorhanden. Sie können Wörter hinzufügen, indem Sie das \"+\"-Symbol berühren."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Für alle Sprachen"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Weitere Sprachen..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Löschen"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-el/strings-appname.xml b/java/res/values-el/strings-appname.xml
new file mode 100644
index 0000000..4fe7276
--- /dev/null
+++ b/java/res/values-el/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Πληκτρολόγιο Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Ορθογραφικός έλεγχος Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Ρυθμίσεις πληκτρολογίου Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Ρυθμίσεις ορθογραφικού ελέγχου Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index fad1cf5..d0f23d2 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Χωρίς καθυστέρ."</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Προεπιλογή"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>χλστ. δ."</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Προεπιλογή"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Πρόταση ονομάτων επαφών"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Χρησιμοποιήστε ονόματα από τις Επαφές για προτάσεις και διορθ."</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Τελεία με διπλό πάτημα πλήκτρ.διαστ."</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Το διπλό πάτημα του πλήκτρ.διαστ. εισάγει μια τελεία και ένα κενό"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Αυτόματη χρήση κεφαλαίων"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Χρήση κεφαλαίου στην πρώτη λέξη κάθε πρότασης"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Προσωπικό λεξικό"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Πρόσθετα λεξικά"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Κύριο λεξικό"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Εμφάνιση προτάσεων διόρθωσης"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Να εμφανίζεται πάντα"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Εμφάνιση σε κατακόρυφο προσανατολισμό"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Πάντα απόκρυψη"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Αποκλεισμός υβριστικών λέξεων"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Να μην προτείνονται πιθανώς προσβλητικές λέξεις"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Αυτόματη διόρθωση"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Τα πλήκτρα διαστήματος και στίξης διορθ. αυτόμ. λάθος λέξεις"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Απενεργοποίηση"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Να εγκατασταθεί όντως αυτό το αρχείο για <xliff:g id="LOCALE_NAME">%s</xliff:g>;"</string>
     <string name="error" msgid="8940763624668513648">"Παρουσιάστηκε σφάλμα."</string>
     <string name="button_default" msgid="3988017840431881491">"Προεπιλογή"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Γλώσσα και εισαγωγή"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Επιλογή μεθόδου εισαγωγής"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Καλώς ορίσατε στο <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"με Πληκτρολόγηση με κίνηση"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Έναρξη"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Επόμενο βήμα"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Ρύθμιση της εφαρμογής <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Ενεργοποιήστε την εφαρμογή <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Επιλέξτε \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" στις ρυθμίσεις Γλώσσα και εισαγωγή, για να εκτελεστεί στη συσκευή."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Η εφαρμογή <xliff:g id="APPLICATION_NAME">%s</xliff:g> έχει ενεργοποιηθεί στις Ρυθμίσεις γλώσσας και εισαγωγής, συνεπώς το βήμα έχει ολοκληρωθεί. Πάμε στο επόμενο!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Ενεργοποίηση στις Ρυθμίσεις"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Μετάβαση στην εφαρμογή <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Στη συνέχεια, επιλέξτε \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ως την ενεργή μέθοδο εισαγωγής κειμένου."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Εναλλαγή μεθόδων εισαγωγής"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Συγχαρητήρια, είστε έτοιμοι!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Πλέον μπορείτε να πληκτρολογήσετε όλες τις αγαπημένες σας εφαρμογές με το <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Διαμόρφωση πρόσθετων γλωσσών"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Ολοκληρώθηκε"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Εμφάνιση εικονιδίου εφαρμογής"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Εμφάνιση εικονιδίου εφαρμογής στο πρόγραμμα εκκίνησης"</string>
     <string name="app_name" msgid="6320102637491234792">"Παροχέας λεξικού"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Παροχέας λεξικού"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Υπηρεσία λεξικού"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Υπάρχει διαθέσιμο λεξικό για τα <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Πατήστε για έλεγχο και λήψη"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Λήψη: Οι προτάσεις για τα <xliff:g id="LANGUAGE">%1$s</xliff:g> θα είναι έτοιμες σύντομα."</string>
+    <string name="version_text" msgid="2715354215568469385">"Έκδοση <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Προσθήκη"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Προσθήκη στο λεξικό"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Φράση"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Περισσ. επιλογές"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Λιγότ. επιλογές"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Λέξη:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Συντόμευση:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Γλώσσα:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Πληκτρολογήστε μια λέξη"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Προαιρετική συντόμευση"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Επεξεργασία λέξης"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Επεξεργασία"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Διαγραφή"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Δεν υπάρχουν λέξεις καταχωρισμένες στο λεξικό χρήστη. Προσθέστε μια λέξη πατώντας το κουμπί Προσθήκη (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Για όλες τις γλώσσες"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Περισσότερες γλώσσες…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Διαγραφή"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-en-rGB/dictionary-pack.xml b/java/res/values-en-rGB/strings-appname.xml
similarity index 64%
rename from java/res/values-en-rGB/dictionary-pack.xml
rename to java/res/values-en-rGB/strings-appname.xml
index f65d45b..5ad5eae 100644
--- a/java/res/values-en-rGB/dictionary-pack.xml
+++ b/java/res/values-en-rGB/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android Keyboard (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android Spell Checker (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android Keyboard Settings (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android Spell Checker Settings (AOSP)"</string>
 </resources>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index c0b9ede..df29be3 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"No delay"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"System default"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggest Contact names"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Use names from Contacts for suggestions and corrections"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Double-space full stop"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Double tap on spacebar inserts a full stop followed by a space"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalisation"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Capitalise the first word of each sentence"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personal dictionary"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Add-on dictionaries"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Main dictionary"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Show correction suggestions"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Always show"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Show in portrait mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Always hide"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Block offensive words"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Do not suggest potentially offensive words"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Auto-correction"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correct mistyped words automatically with spacebar and punctuation"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Really install this file for <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"There was an error"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Language &amp; input"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Choose input method"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Welcome to <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"with Gesture Typing"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Get started"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Next step"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Setting up <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Enable <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Please tick \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" in your Language &amp; input settings. This will authorise it to run on your device."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> is already enabled in your Language &amp; input settings, so this step is done. On to the next one!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Enable in Settings"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Switch to <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Next, select \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" as your active text-input method."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Switch input methods"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Congratulations, you\'re all set!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Now you can type in all your favourite apps with <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configure additional languages"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Finished"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Show app icon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Display application icon in the launcher"</string>
     <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Dictionary Service"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"A dictionary is available for <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Press to review and download"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Downloading: suggestions for <xliff:g id="LANGUAGE">%1$s</xliff:g> will be ready soon."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Add"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Add to dictionary"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Phrase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"More options"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Fewer options"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Word:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Shortcut:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Language:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Type a word"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Optional shortcut"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit word"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Delete"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"You don\'t have any words in the user dictionary. Add a word by touching the Add (+) button."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"For all languages"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"More languages…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Delete"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-es-rUS/dictionary-pack.xml b/java/res/values-es-rUS/strings-appname.xml
similarity index 62%
rename from java/res/values-es-rUS/dictionary-pack.xml
rename to java/res/values-es-rUS/strings-appname.xml
index f65d45b..f0560d9 100644
--- a/java/res/values-es-rUS/dictionary-pack.xml
+++ b/java/res/values-es-rUS/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclado de Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Corrector ortográfico de Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Configuración del teclado de Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Config. del corrector ortográfico de Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index e1024e5..d306f21 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sin demora"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminada"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Valor predet. sist."</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nombres de contacto"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Usar nombres de los contactos para sugerencias y correcciones"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punto y doble espacio"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Tocar dos veces la barra espaciadora inserta un punto y espacio."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Escribe con mayúscula la primera palabra de cada frase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Diccionario personal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionarios complementarios"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionario principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Mostrar sugerencias de correcciones"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar en modo de retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palabras ofensivas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"No sugerir posibles palabras ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Corrección automática"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"La barra espaciadora y las teclas de puntuación insertan automáticamente la palabra corregida"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivado"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"¿Realmente quieres instalar este archivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Se produjo un error."</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminado"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Teclado e idioma"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Seleccionar método de entrada"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Te damos la bienvenida a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"con escritura gestual"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Comenzar"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Siguiente paso"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurando <xliff:g id="APPLICATION_NAME">%s</xliff:g>…"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Habilitar <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Marca \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" en Teclado e idioma para permitir que se ejecute en el dispositivo."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"La aplicación <xliff:g id="APPLICATION_NAME">%s</xliff:g> ya está habilitada en Teclado e idioma, por lo que este paso está finalizado. Pasemos al siguiente."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Habilitar en Configuración"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Cambiar a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"A continuación, selecciona \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" como tu método de entrada de texto activo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Cambiar métodos de entrada"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"¡Felicitaciones, ya terminaste!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Ahora puedes escribir en todas las aplicaciones que quieras con <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurar otros idiomas"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Listo"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostrar ícono de aplicación"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostrar ícono de aplicación en el selector"</string>
     <string name="app_name" msgid="6320102637491234792">"Proveedor de diccionarios"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Proveedor de diccionarios"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Servicio de diccionarios"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Hay un diccionario disponible de <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pulsar para opinar y descargar"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Descargando: las sugerencias de <xliff:g id="LANGUAGE">%1$s</xliff:g> estarán disponibles en breve."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versión <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Agregar"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Agregar al diccionario"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Más opciones"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menos opciones"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Aceptar"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Palabra:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Acceso directo:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Escribe una palabra."</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Acceso directo opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editar palabra"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editar"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Eliminar"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"El diccionario del usuario no contiene ninguna palabra. Para agregar una palabra, toca el botón Agregar (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Para todos los idiomas"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Más idiomas"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Eliminar"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-es/dictionary-pack.xml b/java/res/values-es/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-es/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-es/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-es/strings-appname.xml
index f65d45b..e0059f9 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-es/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclado Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Corrector de Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Ajustes del teclado de Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Ajustes del corrector de Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 6dd9d59..117ac95 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sin retraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predeterminado"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Predeterminado"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir contactos"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizar nombres de contactos para sugerencias y correcciones"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punto y espacio"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Si tocas dos veces el espacio, se inserta un punto seguido de un espacio"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Mayúsculas automáticas"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Escribir la primera letra de cada palabra en mayúscula"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Diccionario personal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Diccionarios complementarios"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Diccionario principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Sugerencias de correcciones"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar siempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar en modo vertical"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar siempre"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palabras ofensivas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"No sugerir palabras potencialmente ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autocorrección"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Pulsar la tecla de espacio o punto para corregir errores"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desactivada"</string>
@@ -88,7 +88,7 @@
     <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string>
     <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Mayúsculas activadas (tocar para inhabilitar)"</string>
     <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Bloqueo de mayúsculas activado (tocar para inhabilitar)"</string>
-    <string name="spoken_description_delete" msgid="8740376944276199801">"Suprimir"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Eliminar"</string>
     <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string>
     <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string>
     <string name="spoken_description_to_numeric" msgid="591752092685161732">"Números"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"¿Seguro que quieres instalar este archivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Se ha producido un error"</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminado"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Idioma e introducción de texto"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Selecciona un método de entrada"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Te damos la bienvenida a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"con escritura gestual"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Empezar"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Siguiente paso"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurando <xliff:g id="APPLICATION_NAME">%s</xliff:g>..."</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Habilitar <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Selecciona <xliff:g id="APPLICATION_NAME">%s</xliff:g> en Idioma e introducción de texto para que pueda usarse en tu dispositivo."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"La aplicación <xliff:g id="APPLICATION_NAME">%s</xliff:g> ya está habilitada en los ajustes de idioma e introducción de texto. Ahora pasemos al siguiente paso."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Habilitar en Ajustes"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Cambiar a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"A continuación, selecciona <xliff:g id="APPLICATION_NAME">%s</xliff:g> como método de introducción de texto activo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Alternar métodos de entrada"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"¡Enhorabuena, has terminado!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Ahora puedes escribir en todas tus aplicaciones favoritas con <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configura otros idiomas"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Listo"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostrar icono de aplicación"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostrar icono de aplicación en menú de aplicaciones"</string>
     <string name="app_name" msgid="6320102637491234792">"Proveedor del diccionario"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Proveedor del diccionario"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Servicio de diccionario"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Hay un diccionario disponible de <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pulsa para comprobar y descargar"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Descargando: las sugerencias de <xliff:g id="LANGUAGE">%1$s</xliff:g> estarán disponibles en breve."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versión <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Añadir"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Añadir al diccionario"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Más opciones"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menos opciones"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Aceptar"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Palabra:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Acceso directo:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Escribe una palabra"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Acceso directo opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editar palabra"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editar"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Eliminar"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"No tienes ninguna palabra en el diccionario del usuario. Toca el botón Añadir (+) para añadir una palabra."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Para todos los idiomas"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Más idiomas…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Eliminar"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNÑOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-et/dictionary-pack.xml b/java/res/values-et/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-et/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-et/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-et/strings-appname.xml
index f65d45b..bbc13d2 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-et/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Androidi klaviatuur (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Androidi õigekirjakontroll (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Androidi klaviatuuri seaded (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Androidi õigekirjakontrolli seaded (AOSP)"</string>
 </resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index fc85472..e7c7ccf 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Viivituseta"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Vaikeseade"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Süsteemi vaikeväärt."</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Soovita kontaktkirjeid"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Kasuta soovitusteks ja parandusteks nimesid kontaktiloendist"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punkt tühikuklahviga"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Tühikuklahvi kaks korda puudutades sisestatakse punkt ja tühik"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaatne suurtähtede kasutamine"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Iga lause esimese sõna kirjutamine suure algustähega"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Isiklik sõnastik"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Pistiksõnaraamatud"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Peamine sõnaraamat"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Kuva parandussoovitusi"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Kuva alati"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Kuva vertikaalrežiimis"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Peida alati"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokeeri solvavad sõnad"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ära soovita potentsiaals. solvavaid sõnu"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automaatparandus"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tühik ja kirjavahemärgid parand. autom. kirjavigadega sõnad"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Väljas"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Kas soovite tõesti installida faili lokaadile <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ilmnes viga"</string>
     <string name="button_default" msgid="3988017840431881491">"Vaikeväärtus"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Keeled ja sisestamine"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Valige sisestusmeetod"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Tere tulemast rakendusse <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"joonistusega sisestamisega"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Alustamine"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Järgmine toiming"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Rakenduse <xliff:g id="APPLICATION_NAME">%s</xliff:g> seadistamine"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Lubage <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Märkige oma keele ja sisestamise seadetes rakendus „<xliff:g id="APPLICATION_NAME">%s</xliff:g>”. See lubab rakenduse käitamise teie seadmes."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> on teie keele- ja sisestusseadetes juba lubatud, seega on see toiming tehtud. Asuge järgmise toimingu juurde."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Luba seadetes"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Minge üle rakendusele <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Järgmisena valige aktiivseks tekstisisestusmeetodiks rakendus „<xliff:g id="APPLICATION_NAME">%s</xliff:g>”."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Vaheta sisestusmeetodeid"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Õnnitleme. Kõik on valmis!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nüüd saate rakendusega <xliff:g id="APPLICATION_NAME">%s</xliff:g> sisestada kõikides oma lemmikrakendustes."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Seadista lisakeeled"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Lõpetatud"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Kuva rakenduse ikoon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Rakenduse ikooni kuvamine käivitajas"</string>
     <string name="app_name" msgid="6320102637491234792">"Sõnastikupakkuja"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Sõnastikupakkuja"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Sõnastikuteenus"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Sõnastik on <xliff:g id="LANGUAGE">%1$s</xliff:g> keele jaoks saadaval"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Vajutage ülevaatamiseks ja allalaadimiseks"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Allalaadimine: <xliff:g id="LANGUAGE">%1$s</xliff:g> keele soovitused on varsti saadaval."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versioon <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Lisa"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Sõnaraamatusse lisamine"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fraas"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Rohkem valikuid"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Vähem valikuid"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Sõna:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Otsetee:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Keel:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Sisestage sõna"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valikuline otsetee"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Sõna muutmine"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Muuda"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Kustuta"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Teie kasutaja sõnaraamatus ei ole ühtegi sõna. Sõna saate lisada, puudutades nuppu Lisa (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Kõikides keeltes"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Rohkem keeli ..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Kustuta"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSŠZŽTUVWÕÄÖÜXY"</string>
 </resources>
diff --git a/java/res/values-fa/dictionary-pack.xml b/java/res/values-fa/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-fa/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-fa/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-fa/strings-appname.xml
index f65d45b..38234c2 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-fa/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"صفحه کلید Android ‏(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"غلط‌گیر Android ‏(AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"تنظیمات صفحه کلید Android ‏(AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"تنظیمات غلط‌گیر Android ‏(AOSP)"</string>
 </resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index e7f5c96..302f097 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"بدون تأخیر"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"پیش‌فرض"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> میلی‌ثانیه"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"پیش‌فرض سیستم"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"پیشنهاد نام‌های مخاطب"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"برای پیشنهاد و تصحیح از نام مخاطبین استفاده شود"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"نقطه با دو فاصله"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"با دوبار ضربه روی دکمه فاصله نقطه با یک فاصله بعد آن درج می‌شود"</string>
     <string name="auto_cap" msgid="1719746674854628252">"بزرگ‌کردن خودکار حروف"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"بزرگ‌نویسی کلمه اول هر جمله"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"فرهنگ لغت شخصی"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"فرهنگ‌های لغت افزودنی"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"فرهنگ‌ لغت اصلی"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"نمایش پیشنهادات تصحیح"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"همیشه نمایش داده شود"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"نمایش در حالت عمودی"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"همیشه پنهان شود"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"عدم نمایش کلمات توهین‌آمیز"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"کلمات توهین‌آمیز احتمالی پیشنهاد نشود"</string>
     <string name="auto_correction" msgid="7630720885194996950">"تصحیح خودکار"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"کلید فاصله و علائم نگارشی به صورت خودکار کلماتی را که غلط تایپ شده‌اند تصحیح می‌کنند"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"خاموش"</string>
@@ -175,8 +175,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"این فایل واقعاً برای <xliff:g id="LOCALE_NAME">%s</xliff:g> نصب شود؟"</string>
     <string name="error" msgid="8940763624668513648">"خطایی روی داد"</string>
     <string name="button_default" msgid="3988017840431881491">"پیش‌فرض"</string>
-    <string name="language_settings" msgid="1671153053201809031">"زبان و ورودی"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"انتخاب روش ورودی"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"به <xliff:g id="APPLICATION_NAME">%s</xliff:g> خوش آمدید"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"با ورودی اشاره‌ای"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"شروع به کار"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"مرحله بعد"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"راه‌اندازی <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"فعال‌سازی <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"لطفاً «<xliff:g id="APPLICATION_NAME">%s</xliff:g>» را در تنظیمات زبان و ورودی خود علامت بزنید. این کار مجوز اجرای آن در دستگاه شما است."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> در حال حاضر در تنظیمات زبان و ورودی شما فعال است، بنابراین این مرحله انجام شده است. به مرحله بعد بروید!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"فعال‌سازی در تنظیمات"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"جابجایی به <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"در مرحله بعد، با انتخاب «<xliff:g id="APPLICATION_NAME">%s</xliff:g>» به عنوان روش ورودی نوشتار خود آن را فعال نمایید."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"تغییر روش‌های ورودی"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"تبریک می‌گوییم، اکنون کاملاً آماده هستید!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"اکنون می‌توانید در همه برنامه‌های دلخواه خود با <xliff:g id="APPLICATION_NAME">%s</xliff:g> تایپ کنید."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"پیکربندی زبان‌های دیگر"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"تمام شد"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"نمایش نماد برنامه"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"نمایش نماد برنامه در راه‌انداز"</string>
     <string name="app_name" msgid="6320102637491234792">"ارائه‌دهنده فرهنگ لغت"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"ارائه‌دهنده فرهنگ لغت"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"سرویس فرهنگ لغت"</string>
@@ -207,4 +223,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"یک فرهنگ لغت برای <xliff:g id="LANGUAGE">%1$s</xliff:g> موجود است"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"برای مرور و دانلود فشار دهید"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"دانلود لغات پیشنهادی برای <xliff:g id="LANGUAGE">%1$s</xliff:g> به زودی شروع می‌شود."</string>
+    <string name="version_text" msgid="2715354215568469385">"نسخه <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"افرودن"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"افزودن به فرهنگ‌ لغت"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"عبارت"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"گزینه‌های بیشتر"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"گزینه‌های کمتر"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"تأیید"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"کلمه:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"میانبر:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"زبان:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"یک کلمه تایپ کنید"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"میانبر اختیاری"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"ویرایش کلمه"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"ویرایش"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"حذف"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"کلمه‌ای در فرهنگ لغت کاربر شما موجود نیست. می‌توانید با لمس کردن دکمه افزودن (+) یک کلمه را اضافه کنید."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"برای همه زبان‌ها"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"زبان‌های بیشتر…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"حذف"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-fi/dictionary-pack.xml b/java/res/values-fi/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-fi/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-fi/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-fi/strings-appname.xml
index f65d45b..8ee3ae4 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-fi/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-näppäimistö (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-oikoluku (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android-näppäimistön asetukset (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android-oikoluvun asetukset (AOSP)"</string>
 </resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 6499136..a208048 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Ei viivettä"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Oletus"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Järjest. oletusarvo"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Ehdota yhteystietojen nimiä"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Käytä yhteystietojen nimiä ehdotuksissa ja korjauksissa"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Kaksoisvälilyönti = piste"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Välilyönnin kaksoisnapautus lisää tekstiin pisteen ja välilyönnin"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automaattiset isot kirjaimet"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Kirjoita jokaisen lauseen ensimmäinen sana isolla alkukirjaimella"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Oma sanakirja"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Lisäsanakirjat"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pääsanakirja"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Näytä korjausehdotukset"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Näytä aina"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Näytä pystyasennossa"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Piilota aina"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Estä loukkaavat sanat"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Älä ehdota mahdollisesti loukkaavia sanoja"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autom. korjaus"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Välilyönnit ja välimerkit korjaavat väärinkirjoitetut sanat automaattisesti"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Älä käytä"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Haluatko asentaa tämä tiedoston kielelle <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Tapahtui virhe"</string>
     <string name="button_default" msgid="3988017840431881491">"Oletusarvot"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Kieli ja syöttötapa"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Valitse syöttötapa"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Tervetuloa käyttämään sovellusta <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"sekä piirtokirjoitus"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Aloita"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Seuraava vaihe"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Sovelluksen <xliff:g id="APPLICATION_NAME">%s</xliff:g> asetukset"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Ota <xliff:g id="APPLICATION_NAME">%s</xliff:g> käyttöön"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Valitse <xliff:g id="APPLICATION_NAME">%s</xliff:g> kieli- ja syöttötapa-asetuksissa, mikä valtuuttaa sovel. laitteellesi."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> on jo käytössä Kieli- ja syöttöasetuksissa, joten tämä vaihe on tehty. Siirrytään eteenpäin!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Ota käyttöön asetuksissa"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Siirry sovellukseen <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Valitse <xliff:g id="APPLICATION_NAME">%s</xliff:g> käytössä olevaksi tekstinsyöttötavaksi."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Vaihda syöttötapaa"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Onneksi olkoon, valmista tuli!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nyt voit kirjoittaa kaikkiin lempisovelluksiisi sovelluksen <xliff:g id="APPLICATION_NAME">%s</xliff:g> avulla."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Määritä lisää kieliä"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Valmis"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Näytä sovelluskuvake"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Näytä sovelluskuvake käynnistysohjelmassa."</string>
     <string name="app_name" msgid="6320102637491234792">"Sanakirjan tarjoaja"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Sanakirjan tarjoaja"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Sanakirjapalvelu"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Kielen <xliff:g id="LANGUAGE">%1$s</xliff:g> sanakirja on saatavilla"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Paina tätä, jos haluat tarkastella kohdetta tai ladata sen"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Ladataan: pian ehdotuksia näytetään kielellä <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versio <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Lisää"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Lisää sanakirjaan"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Ilmaus"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Lisäasetukset"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Väh. vaihtoeht."</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Sana:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pikanäppäin"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Kieli:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Kirjoita sana"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valinnainen pikanäppäin"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Muokkaa sanaa"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Muokkaa"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Poista"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Käyttäjän sanakirjassa ei ole yhtään sanaa. Voit lisätä sanan koskettamalla Lisää (+) -painiketta."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Kaikille kielille"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Lisää kieliä…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Poista"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ"</string>
 </resources>
diff --git a/java/res/values-fr/dictionary-pack.xml b/java/res/values-fr/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-fr/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index 2e916a7..f064411 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -19,9 +19,9 @@
 -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
-    <string name="symbols_preceded_by_space">([{*&amp;;:!?</string>
+    <string name="symbols_preceded_by_space">([{&amp;;:!?</string>
     <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
-    <string name="symbols_followed_by_space">.,;:!?)]}*&amp;</string>
+    <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
     <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-fr/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-fr/strings-appname.xml
index f65d45b..d45e239 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-fr/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Clavier Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Correcteur orthographique Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Paramètres du clavier Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Paramètres du correcteur orthographique Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 4c6ae70..9b585a1 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sans délai"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Par défaut"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Paramètres par défaut"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Proposer noms de contacts"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utiliser des noms de contacts pour les suggestions et corrections"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Point et espace"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Appuyez deux fois sur la barre d\'espace pour insérer un point et un espace."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Majuscules auto"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Majuscule au premier mot de chaque phrase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dictionnaire personnel"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dictionnaires complémentaires"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dictionnaire principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Suggestions de correction"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Toujours afficher"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Afficher en mode Portrait"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Toujours masquer"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquer les termes choquants"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Pas de termes potentiellement choquants"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correction auto"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corriger autom. orthographe (pression sur barre espace/signes ponctuation)"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Désactiver"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Installer ce fichier pour la langue \"<xliff:g id="LOCALE_NAME">%s</xliff:g>\" ?"</string>
     <string name="error" msgid="8940763624668513648">"Une erreur s\'est produite"</string>
     <string name="button_default" msgid="3988017840431881491">"Par défaut"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Langue et saisie"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Sélectionnez le mode de saisie"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Bienvenue dans <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"avec la saisie gestuelle"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Commencer"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Étape suivante"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Activer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sous \"Langue et saisie\", cochez \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" pour autoriser son exécution sur l\'appareil."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"L\'application \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" est déjà activée dans vos paramètres \"Langue et saisie\". Passez à l\'étape suivante."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Activer le clavier dans les paramètres"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Basculer vers <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Sélectionnez ensuite \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" comme mode de saisie actif."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Changer de mode de saisie"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Félicitations, l\'opération est terminée"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Avec <xliff:g id="APPLICATION_NAME">%s</xliff:g>, vous pouvez saisir du texte dans toutes vos applications préférées."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurer des langues supplémentaires"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"OK"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Afficher icône application"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Afficher l\'icône de l\'application dans le lanceur"</string>
     <string name="app_name" msgid="6320102637491234792">"Fournisseur de dictionnaires"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Fournisseur de dictionnaires"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Service de dictionnaires"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Un dictionnaire est disponible en <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Appuyez ici pour consulter et télécharger le dictionnaire."</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"En cours de téléchargement. Des suggestions pour la langue suivante seront bientôt disponibles : <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Ajouter"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Ajouter au dictionnaire"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Expression"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Plus d\'options"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Moins d\'options"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Mot :"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Raccourci :"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Langue :"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Saisissez un mot"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Raccourci facultatif"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Modifier le mot"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Modifier"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Supprimer"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Votre dictionnaire personnel ne contient aucun mot. Ajoutez un mot en appuyant sur le bouton d\'ajout (\"+\")."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Pour toutes les langues"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Plus de langues…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Supprimer"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-hi/dictionary-pack.xml b/java/res/values-hi/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-hi/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-hi/strings-appname.xml b/java/res/values-hi/strings-appname.xml
new file mode 100644
index 0000000..59e33e6
--- /dev/null
+++ b/java/res/values-hi/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android कीबोर्ड (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android वर्तनी परीक्षक (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android कीबोर्ड सेटिंग (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android वर्तनी परीक्षक सेटिंग (AOSP)"</string>
+</resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index bd745cc..8222f58 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"कोई विलंब नहीं"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"डिफ़ॉल्ट"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> मिलीसेकंड"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"सिस्टम डिफ़ॉल्ट"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"संपर्क नाम सुझाएं"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"सुझाव और सुधार के लिए संपर्क से नामों का उपयोग करें"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"दोहरे स्पेस वाला पीरियड"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"स्पेसबार पर डबल टैप करने से पीरियड शामिल हो जाता है जिसके बाद एक रिक्ति होती है"</string>
     <string name="auto_cap" msgid="1719746674854628252">"स्‍वत: अक्षर बड़े करना"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"प्रत्येक वाक्य के पहले शब्द को बड़ा लिखें"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"व्यक्तिगत डिक्शनरी"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"एड-ऑन डिक्शनरी"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"मुख्‍य डिक्‍शनरी"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"सुधार सुझाव दिखाएं"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"हमेशा दिखाएं"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"पोर्ट्रेट मोड में दिखाएं"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"हमेशा छुपाएं"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"आपत्तिजनक शब्द अवरुद्ध करें"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"संभावित आपत्तिजनक शब्दों का सुझाव न दें"</string>
     <string name="auto_correction" msgid="7630720885194996950">"स्‍वत: सुधार"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacebar और विराम चिह्न गलत लिखे गए शब्‍दों को स्‍वचालित रूप से ठीक करते हैं"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"बंद"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> के लिए वास्तव में यह फ़ाइल इंस्टॉल करें?"</string>
     <string name="error" msgid="8940763624668513648">"कोई त्रुटि हुई थी"</string>
     <string name="button_default" msgid="3988017840431881491">"डिफ़ॉल्ट"</string>
-    <string name="language_settings" msgid="1671153053201809031">"भाषा और इनपुट"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"इनपुट पद्धति चुनें"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> में आपका स्वागत है"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"हावभाव लेखन के साथ"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"आरंभ करें"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"अगला चरण"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> सेट करना"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> को सक्षम करें"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"कृपया अपनी भाषा और इनपुट सेटिंग में \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" को चेक करें. इससे वह आपके उपकरण पर चलने के लिए अधिकृत हो जाएगा."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> आपकी भाषा और इनपुट सेटिंग में पहले से सक्षम है, इसलिए यह चरण पूर्ण हो गया है. अगले चरण पर जाएं!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"सेटिंग में सक्षम करें"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> पर स्विच करें"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"इसके बाद, \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" को अपनी सक्रिय पाठ-इनपुट पद्धति के रूप में चुनें."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"इनपुट पद्धतियां स्विच करें"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"बधाई हो, आप बिल्कुल तैयार हैं!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"अब आप <xliff:g id="APPLICATION_NAME">%s</xliff:g> के साथ अपने सभी पसंदीदा एप्लिकेशन में लिख सकते हैं."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"अतिरिक्त भाषाएं कॉन्फ़िगर करना"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"समाप्त"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"एप्लिकेशन आइकन दिखाएं"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"लॉन्चर में एप्लिकेशन आइकन प्रदर्शित करें"</string>
     <string name="app_name" msgid="6320102637491234792">"डिक्‍शनरी प्रदाता"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"डिक्‍शनरी प्रदाता"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"डिक्‍शनरी सेवा"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> के लिए डिक्‍शनरी उपलब्‍ध है"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"समीक्षा करने और डाउनलोड करने के लिए दबाएं"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"डाउनलोड हो रहा है: <xliff:g id="LANGUAGE">%1$s</xliff:g> के लिए सुझाव जल्दी ही तैयार हो जाएंगे."</string>
+    <string name="version_text" msgid="2715354215568469385">"संस्करण <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"जोड़ें"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"शब्दकोष में जोड़ें"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"वाक्यांश"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"अधिक विकल्प"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"कम विकल्‍प"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ठीक"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"शब्द:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"शॉर्टकट:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"भाषा:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"कोई शब्द लिखें"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"वैकल्पिक शॉर्टकट"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"शब्‍द संपादित करें"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"संपादित करें"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"हटाएं"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"आपके पास उपयोगकर्ता शब्दकोश में कोई शब्‍द नहीं है. जोड़ें (+) बटन स्‍पर्श करके कोई शब्‍द जोड़ें."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"सभी भाषाओं के लिए"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"अधिक भाषाएं…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"हटाएं"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-hr/dictionary-pack.xml b/java/res/values-hr/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-hr/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-hr/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-hr/strings-appname.xml
index f65d45b..e22d2a2 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-hr/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Androidova tipkovnica (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Androidova provjera pravopisa (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Postavke Androidove tipkovnice (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Postavke Androidove provjere pravopisa (AOSP)"</string>
 </resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 6b418a1..3852572 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez odgode"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Zadano"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Zadano sustavom"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Predlaži imena kontakata"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Upotreba imena iz Kontakata za prijedloge i ispravke"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Točka s dva razmaka"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dvostrukim dodirivanjem razmaknice umeću se točka i razmak"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatsko pisanje velikih slova"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Napiši velikim slovom prvu riječ svake rečenice"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Osobni rječnik"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Rječnici-dodaci"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Glavni rječnik"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Pokaži prijedloge ispravka"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Uvijek prikaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Prikaži u portretnom načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Uvijek sakrij"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokiraj uvredljive riječi"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ne predlaži potencijalno uvredljive riječi"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatski ispravak"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Razmak i interpunkcija automatski ispravljaju krive riječi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Isključeno"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Želite li doista instalirati ovu datoteku za <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Došlo je do pogreške"</string>
     <string name="button_default" msgid="3988017840431881491">"Zadano"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Jezik i unos"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Odabir načina unosa"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Dobro došli u aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"s Pisanjem kretnjama"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Počnite s radom"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Sljedeći korak"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Postavljanje aplikacije <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Omogućite aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Potvrdite aplikaciju \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" u postavkama Jezik i unos i ovlastite je za pokretanje."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Aplikacija <xliff:g id="APPLICATION_NAME">%s</xliff:g> već je omogućena u postavkama jezika i unosa, pa je taj korak gotov. Nastavite sa sljedećim korakom!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Omogući u postavkama"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Prijeđite na aplikaciju <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Zatim odaberite aplikaciju \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" kao aktivan način unosa teksta."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Prebaci na drugi način unosa"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Izvrsno, spremni ste!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Sada možete pisati u svim svojim omiljenim aplikacijama pomoću aplikacije <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfiguriraj dodatne jezike"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Završeno"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Prikaži ikonu aplikacije"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Prikazivanje ikone aplikacije u pokretaču"</string>
     <string name="app_name" msgid="6320102637491234792">"Davatelj rječnika"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Davatelj rječnika"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Usluga rječnika"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Dostupan je rječnik za <xliff:g id="LANGUAGE">%1$s</xliff:g> jezik"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pritisnite za pregled i preuzimanje"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Preuzimanje: prijedlozi za <xliff:g id="LANGUAGE">%1$s</xliff:g> bit će spremni uskoro."</string>
+    <string name="version_text" msgid="2715354215568469385">"Verzija <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Dodavanje"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Dodaj u rječnik"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fraza"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Više opcija"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Manje opcija"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"U redu"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Riječ:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Prečac:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Jezik:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Upišite riječ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Neobavezni prečac"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Uređivanje riječi"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Uređivanje"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Brisanje"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Nemate nijednu riječ u korisničkom rječniku. Riječ možete dodati dodirom na gumb Dodaj (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Za sve jezike"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Više jezika…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Izbriši"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCČĆDDŽĐEFGHIJKLLJMNNJOPRSŠTUVZŽ"</string>
 </resources>
diff --git a/java/res/values-hu/dictionary-pack.xml b/java/res/values-hu/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-hu/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-hu/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-hu/strings-appname.xml
index f65d45b..ec99904 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-hu/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-billentyűzet (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Androidos helyesírás-ellenőrző (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android-billentyűzet beállításai (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Androidos helyesírás-ellenőrző beállításai (AOSP)"</string>
 </resources>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index b35a5f1..5394cc2 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Nincs késés"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Alapbeállítás"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Alapértelmezett"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Javasolt névjegyek"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"A névjegyek használata a javaslatokhoz és javításokhoz"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dupla szóköz: pont"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"A szóköz kétszeri megérintése beszúr egy pontot, majd egy szóközt"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatikusan nagy kezdőbetű"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Minden mondat első szava nagybetűvel"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Személyes szótár"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Bővítmények: szótárak"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Fő szótár"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Javítási ajánlások megjelenítése"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mindig látszik"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Megjelenítés álló tájolásban"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Mindig rejtve"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Sértő szavak kizárása"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ne javasoljon esetlegesen sértő szavakat"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatikus javítás"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Szóköz és központozás automatikusan javítja az elgépelést"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Ki"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Valóban telepíti ezt a fájlt <xliff:g id="LOCALE_NAME">%s</xliff:g> nyelvhez?"</string>
     <string name="error" msgid="8940763624668513648">"Hiba történt."</string>
     <string name="button_default" msgid="3988017840431881491">"Alapértelmezett"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Nyelv és bevitel"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Beviteli mód kiválasztása"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Üdvözli a(z) <xliff:g id="APPLICATION_NAME">%s</xliff:g>!"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"kézmozdulatokkal történő bevitellel"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Első lépések"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Következő lépés"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"A(z) <xliff:g id="APPLICATION_NAME">%s</xliff:g> beállítása"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"A(z) <xliff:g id="APPLICATION_NAME">%s</xliff:g> engedélyezése"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Jelölje be a(z) „<xliff:g id="APPLICATION_NAME">%s</xliff:g>” alkalmazást a „Nyelv és bevitel” alatt a futtatás érdekében."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> alkalmazást már engedélyezte a Nyelv és bevitel beállításainál, tehát ez a lépés már kész. Folytassa a következővel!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Engedélyezés a Beállítások között"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Váltás a(z) <xliff:g id="APPLICATION_NAME">%s</xliff:g> alkalmazásra"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Ezután válassza a(z) „<xliff:g id="APPLICATION_NAME">%s</xliff:g>”  alkalmazást aktív szövegbeviteli módszerként."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Váltás a beviteli módok között"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gratulálunk, máris elkészült!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Immár minden kedvenc alkalmazásában gépelhet a(z) <xliff:g id="APPLICATION_NAME">%s</xliff:g> segítségével."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Állítson be további nyelveket"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Befejeződött"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Alkalmazásikon megjelenítése"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Alkalmazásikon megjelenítése az indítóban"</string>
     <string name="app_name" msgid="6320102637491234792">"Szótárszolgáltató"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Szótárszolgáltató"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Szótárszolgáltatás"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> nyelvhez van rendelkezésre álló szótár"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Nyomja meg az áttekintéshez és letöltéshez"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Letöltés: a(z) <xliff:g id="LANGUAGE">%1$s</xliff:g> nyelvvel kapcsolatos javaslatok hamarosan elérhetők lesznek."</string>
+    <string name="version_text" msgid="2715354215568469385">"Verzió: <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Hozzáadás"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Hozzáadás a szótárhoz"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Kifejezés"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"További opciók"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Kevesebb opció"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Szó:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Gyorsparancs:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Nyelv:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Írjon be egy szót"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Választható gyorsparancs"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Szó szerkesztése"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Szerkesztés"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Törlés"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Nincsenek szavak a felhasználói szótárban. Új szavakat a Hozzáadás (+) gomb megérintésével vehet fel."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Minden nyelven"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"További nyelvek…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Törlés"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-in/dictionary-pack.xml b/java/res/values-in/strings-appname.xml
similarity index 64%
rename from java/res/values-in/dictionary-pack.xml
rename to java/res/values-in/strings-appname.xml
index f65d45b..c9da0db 100644
--- a/java/res/values-in/dictionary-pack.xml
+++ b/java/res/values-in/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Keyboard Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Pemeriksa Ejaan Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Setelan Keyboard Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Setelan Pemeriksa Ejaan Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 5072b9a..47d66dc 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tanpa penundaan"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> md"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Default sistem"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sarankan nama Kontak"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama dari Kontak untuk saran dan koreksi"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Titik spasi ganda"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Mengetuk tombol spasi dua kali akan memasukkan titik diikuti satu spasi"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Kapitalisasi otomatis"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Kapitalisasi kata pertama di setiap kalimat"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Kamus pribadi"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Kamus pengaya"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamus utama"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Tampilkan saran koreksi"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Selalu tampilkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Tampilkan dalam mode potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Selalu sembunyikan"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokir kata tak pantas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Jangan sarankan kata yang berpotensi menyinggung"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Koreksi otomatis"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bilah spasi dan tanda baca secara otomatis dikoreksi pada kata yang salah ketik"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Mati"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Yakin ingin memasang file ini untuk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Terjadi kesalahan"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Bahasa &amp; masukan"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Pilih metode masukan"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Selamat datang di <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"dengan Ketikan Isyarat"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Memulai"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Langkah berikutnya"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Menyiapkan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Aktifkan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Centang \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" di setelan Bahasa &amp; masukan Anda. Tindakan ini akan mengizinkannya berjalan di perangkat Anda."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> telah diaktifkan di setelan Bahasa &amp; masukan Anda, jadi langkah ini sudah diselesaikan. Lanjut langkah selanjutnya!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktifkan dalam Setelan"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Beralih ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Lalu, pilih \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" sebagai metode masukan teks aktif Anda."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Alihkan metode masukan"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Selamat, Anda sudah siap!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Sekarang Anda dapat mengetik di semua aplikasi favorit Anda dengan <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurasikan bahasa tambahan"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Selesai"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Tampilkan ikon aplikasi"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Menampillkan ikon aplikasi di peluncur"</string>
     <string name="app_name" msgid="6320102637491234792">"Penyedia Kamus"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Penyedia Kamus"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Layanan Kamus"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Kamus tersedia untuk bahasa <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Tekan untuk meninjau dan mengunduh"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Mengunduh: saran untuk bahasa <xliff:g id="LANGUAGE">%1$s</xliff:g> akan segera tersedia."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versi <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Tambahkan"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Tambahkan ke kamus"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frasa"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Opsi lainnya"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Sedikit opsi"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Oke"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Kata:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pintasan:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Bahasa:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Ketik kata"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Pintasan opsional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit kata"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Hapus"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Anda tidak memiliki satu kata pun dalam kamus pengguna. Tambahkan kata dengan menyentuh tombol Tambahkan (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Untuk semua bahasa"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Bahasa lainnya..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Hapus"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-is/dictionary-pack.xml b/java/res/values-is/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-is/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml
deleted file mode 100644
index 0b00fb2..0000000
--- a/java/res/values-is/strings.xml
+++ /dev/null
@@ -1,454 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, 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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Áfram"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Næsta"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Fyrra"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Lokið"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Senda"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for send_feedback (1780431884109392046) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- 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) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- 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 />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
-    <skip />
-    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
-    <skip />
-    <!-- no translation found for setup_start_action (8936036460897347708) -->
-    <skip />
-    <!-- no translation found for setup_next_action (371821437915144603) -->
-    <skip />
-    <!-- no translation found for setup_steps_title (6400373034871816182) -->
-    <skip />
-    <!-- no translation found for setup_step1_title (3147967630253462315) -->
-    <skip />
-    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
-    <skip />
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
-    <!-- no translation found for setup_step1_action (4366513534999901728) -->
-    <skip />
-    <!-- no translation found for setup_step2_title (6860725447906690594) -->
-    <skip />
-    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
-    <skip />
-    <!-- no translation found for setup_step2_action (1660330307159824337) -->
-    <skip />
-    <!-- no translation found for setup_step3_title (3154757183631490281) -->
-    <skip />
-    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
-    <skip />
-    <!-- no translation found for setup_step3_action (600879797256942259) -->
-    <skip />
-    <!-- no translation found for setup_finish_action (276559243409465389) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
-    <skip />
-    <!-- no translation found for app_name (6320102637491234792) -->
-    <skip />
-    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
-    <skip />
-    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
-    <skip />
-    <!-- no translation found for download_description (6014835283119198591) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
-    <skip />
-    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
-    <skip />
-    <!-- no translation found for user_dictionaries (3582332055892252845) -->
-    <skip />
-    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
-    <skip />
-    <!-- no translation found for dictionary_available (4728975345815214218) -->
-    <skip />
-    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
-    <skip />
-    <!-- no translation found for dictionary_installed (8081558343559342962) -->
-    <skip />
-    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
-    <skip />
-    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
-    <skip />
-    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
-    <skip />
-    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
-    <skip />
-    <!-- no translation found for last_update (730467549913588780) -->
-    <skip />
-    <!-- no translation found for message_updating (4457761393932375219) -->
-    <skip />
-    <!-- no translation found for message_loading (8689096636874758814) -->
-    <skip />
-    <!-- no translation found for main_dict_description (3072821352793492143) -->
-    <skip />
-    <!-- no translation found for cancel (6830980399865683324) -->
-    <skip />
-    <!-- no translation found for install_dict (180852772562189365) -->
-    <skip />
-    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
-    <skip />
-    <!-- no translation found for delete_dict (756853268088330054) -->
-    <skip />
-    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
-    <skip />
-    <!-- no translation found for download_over_metered (1643065851159409546) -->
-    <skip />
-    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
-    <skip />
-    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
-    <skip />
-    <!-- no translation found for version_text (2715354215568469385) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
-</resources>
diff --git a/java/res/values-it/dictionary-pack.xml b/java/res/values-it/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-it/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-it/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-it/strings-appname.xml
index f65d45b..fe5fc97 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-it/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Tastiera Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Controllo ortografico Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Impostazioni tastiera Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Impostazioni controllo ortografico Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index c64543a..8e0d687 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Nessun ritardo"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinito"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Predefinito sistema"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Suggerisci nomi di contatti"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizza nomi di Contatti per suggerimenti e correzioni"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Doppio spazio per punto"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Tocca due volte barra spaziatr. per inserire punto seguito da spazio"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Maiuscole automatiche"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Iniziale maiuscola per la prima parola di ogni frase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dizionario personale"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dizionari aggiuntivi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dizionario principale"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Mostra suggerimenti correzioni"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostra sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostra in modalità verticale"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Nascondi sempre"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blocca parole offensive"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Non suggerire parole potenzialmente offensive"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correzione automatica"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Barra spaziatrice/punteggiatura correggono parole con errori"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Off"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Installare questo file per <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Si è verificato un errore"</string>
     <string name="button_default" msgid="3988017840431881491">"Predefinito"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Lingua e input"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Scegli il metodo di immissione"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Benvenuto in <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"con la Digitazione gestuale"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Inizia"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Passaggio successivo"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurazione di <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Abilita <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Seleziona \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" nelle impostazioni Lingua e immissione per autorizzarne l\'esecuzione sul dispositivo."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> è già abilitato nelle tue impostazioni di lingua e immissione, quindi questo passaggio è completato. Vai al prossimo."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Abilita nelle impostazioni"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Passa a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Quindi seleziona \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" come metodo di immissione testo attivo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Cambia metodo di immissione"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Grazie, adesso sei pronto!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Ora puoi digitare in tutte le tue app preferite con <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configura altre lingue"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Terminato"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostra icona app"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostra l\'icona dell\'app in Avvio applicazioni"</string>
     <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Servizio dizionario"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"È disponibile un dizionario per <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Premi per esaminare e scaricare"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Download: i suggerimenti per <xliff:g id="LANGUAGE">%1$s</xliff:g> saranno pronti a breve."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versione <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Aggiungi"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Aggiungi al dizionario"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Più opzioni"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Meno opzioni"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Parola:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Scorciatoia:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Lingua:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Digita una parola"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Scorciatoia facoltativa"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Modifica parola"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Modifica"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Elimina"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Non sono presenti parole nel dizionario utente. Puoi aggiungere una parola toccando il pulsante Aggiungi (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Per tutte le lingue"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Altre lingue..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Elimina"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-iw/dictionary-pack.xml b/java/res/values-iw/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-iw/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-iw/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-iw/strings-appname.xml
index f65d45b..1a07c54 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-iw/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"מקלדת Android‏ (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"בודק האיות של Android‏ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"הגדרות מקלדת Android‏ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"הגדרות בודק האיות של Android‏ (AOSP)"</string>
 </resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index d476c32..d44f6c4 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ללא עיכוב"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ברירת מחדל"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> אלפ\' שניה"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"ברירת מחדל של המערכת"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"הצע שמות של אנשי קשר"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"השתמש בשמות מרשימת אנשי הקשר עבור הצעות ותיקונים"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"רווח כפול לנקודה"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"הקשה פעמיים על מקש הרווח מזינה נקודה ואחריה רווח"</string>
     <string name="auto_cap" msgid="1719746674854628252">"הפיכת אותיות לרישיות באופן אוטומטי"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"השתמש באות גדולה במילה הראשונה של כל משפט"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"מילון אישי"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"הוספת מילונים"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"מילון ראשי"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"הצג הצעות לתיקונים"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"הצג תמיד"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"הצג בפריסה לאורך"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"הסתר תמיד"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"חסום מילים פוגעניות"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"אל תציע מילים שעלולות להיות פוגעניות"</string>
     <string name="auto_correction" msgid="7630720885194996950">"תיקון אוטומטי"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"מקש הרווח ופיסוק מתקנים אוטומטית שגיאות הקלדה"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"כבוי"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"האם באמת להתקין את הקובץ הזה עבור <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"אירעה שגיאה"</string>
     <string name="button_default" msgid="3988017840431881491">"ברירת מחדל"</string>
-    <string name="language_settings" msgid="1671153053201809031">"שפה וקלט"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"בחירת שיטת קלט"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"ברוך הבא אל <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"עם הקלדת החלקה"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"התחל"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"השלב הבא"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"הגדרת <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"הפעל את <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"סמן את \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" בהגדרות השפה והקלט שלך. פעולה זו תאפשר לו לפעול במכשיר שלך."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> כבר פועל בשפה שלך ובהגדרות הקלט, כך שהשלב הזה הסתיים. הלאה אל השלב הבא!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"הפעל בהגדרות"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"עבור אל <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"בשלב הבא, בחר ב-\'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\' כאמצעי הקלט הפעיל להזנת טקסט."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"החלף שיטות קלט"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"ברכותינו, הכל מוכן!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"כעת תוכל להקליד בכל היישומים המועדפים עליך באמצעות <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"הגדר שפות נוספות"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"סיום"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"הצג את סמל היישום"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"הצג את סמל היישום במפעיל"</string>
     <string name="app_name" msgid="6320102637491234792">"ספק המילון"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"ספק המילון"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"שירות מילון"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"יש מילון זמין עבור <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"לחץ כדי לעיין ולהוריד"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"מוריד: הצעות ב<xliff:g id="LANGUAGE">%1$s</xliff:g> יהיו מוכנות בקרוב."</string>
+    <string name="version_text" msgid="2715354215568469385">"גרסה <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"הוסף"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"הוסף למילון"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ביטוי"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"עוד אפשרויות"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"פחות אפשרויות"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"אישור"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"מילה:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"קיצור דרך:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"שפה:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"הקלד מילה"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"קיצור דרך אופציונלי"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"עריכת מילה"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"ערוך"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"מחק"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"לא מוגדרות מילים במילון המשתמש. הוסף מילה על ידי נגיעה בלחצן \'הוסף\' (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"לכל השפות"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"עוד שפות…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"מחק"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-ja/dictionary-pack.xml b/java/res/values-ja/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ja/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-ja/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-ja/strings-appname.xml
index f65d45b..e3a9303 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-ja/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Androidキーボード(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Androidスペルチェッカー(AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Androidキーボードの設定(AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Androidスペルチェッカーの設定(AOSP)"</string>
 </resources>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 267148c..248c5b9 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"すぐに消去"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"デフォルト"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ミリ秒"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"システムのデフォルト"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"連絡先の名前を候補に表示"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"連絡先の名前を使用して候補表示や自動修正を行います"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"ダブルスペースピリオド"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"スペースバーをダブルタップするとピリオドとスペースを挿入できます"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大文字変換"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"英字入力で各文の最初の単語を大文字にします"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"ユーザー辞書"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"アドオン辞書"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"メイン辞書"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"修正候補を表示する"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"常に表示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"縦向きで表示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"常に非表示"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"不適切な語句をブロック"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"不適切な可能性がある語句を候補にしない"</string>
     <string name="auto_correction" msgid="7630720885194996950">"自動修正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"誤入力をスペースまたは句読点キーで修正する"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"OFF"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"この<xliff:g id="LOCALE_NAME">%s</xliff:g>のファイルをインストールしてもよろしいですか?"</string>
     <string name="error" msgid="8940763624668513648">"エラーが発生しました"</string>
     <string name="button_default" msgid="3988017840431881491">"デフォルト"</string>
-    <string name="language_settings" msgid="1671153053201809031">"言語と入力"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"入力方法の選択"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>へようこそ"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"新しいジェスチャー入力をお試しください"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"開始"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"次のステップ"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>の設定"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>の有効化"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"[言語と入力]で[<xliff:g id="APPLICATION_NAME">%s</xliff:g>]のチェックボックスをオンにしてください。これで、端末でキーボードを使用できるようになります。"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>は[言語と入力]で既に有効になっているので、このステップは完了です。次のステップに進んでください。"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"設定での有効化"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>への切り替え"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"次に、有効なテキスト入力方法として「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」を選択します。"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"入力方法を切り替える"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"設定完了"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"これで、お気に入りのすべてのアプリで<xliff:g id="APPLICATION_NAME">%s</xliff:g>を使用して入力できます。"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"別の言語を設定する"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"完了"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"アプリアイコンを表示"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"ランチャーにアプリアイコンを表示します"</string>
     <string name="app_name" msgid="6320102637491234792">"辞書提供元"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"辞書提供元"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"辞書"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>の辞書を利用できます"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"押すと確認/ダウンロードできます"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ダウンロード中: <xliff:g id="LANGUAGE">%1$s</xliff:g>の入力候補をまもなく利用できます。"</string>
+    <string name="version_text" msgid="2715354215568469385">"バージョン<xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"追加"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"辞書に追加"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"フレーズ"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"詳細設定を表示"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"詳細設定を隠す"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"単語:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"ショートカット:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"言語:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"単語を入力します"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"オプションのショートカット"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"語句の編集"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"編集"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"削除"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"単語リストに登録がありません。追加ボタン[+]をタップすると単語を追加できます。"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"すべての言語用"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"その他の言語…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"削除"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-ka/dictionary-pack.xml b/java/res/values-ka/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ka/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-ka/strings-appname.xml b/java/res/values-ka/strings-appname.xml
new file mode 100644
index 0000000..703c66a
--- /dev/null
+++ b/java/res/values-ka/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-ის კლავიატურა (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-ის მართლწერის შემმოწმებელი (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android-ის კლავიატურის პარამეტრები (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android-ის მართლწერის შემმოწმებლის პარამეტრები (AOSP)"</string>
+</resources>
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
index 7a983e0..5719e6c 100644
--- a/java/res/values-ka/strings.xml
+++ b/java/res/values-ka/strings.xml
@@ -20,435 +20,223 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"გადასვლა"</string>
-    <string name="label_next_key" msgid="362972844525672568">"შემდეგი"</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>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"მართლწერის შემმოწმებელი ერთეულებს თქვენი კონტაქტების სიიდან იყენებს"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"ვიბრაცია კლავიშზე დაჭერისას"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"ხმაური კლავიშზე დაჭერისას"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"გადიდება ღილაკზე დაჭერისას"</string>
+    <string name="general_category" msgid="1859088467017573195">"ზოგადი"</string>
+    <string name="correction_category" msgid="2236750915056607613">"ტექსტის კორექცია"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"ჟესტებით წერა"</string>
+    <string name="misc_category" msgid="6894192814868233453">"სხვა პარამეტრები"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"გაფართუებული პარამეტრები"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"პარამეტრები ექსპერტთათვის"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"შეყვანის სხვა მეთოდებზე გადართვა"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"ენის გადართვის ღილაკს შეყვანის სხვა მეთოდებსაც შეიცავს"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"ენის გადართვის კლავიში"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"აჩვენე, როდესაც ჩართულია სხვადასხვა შეყვანის ენა"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"გასრიალების ინდიკატ. ჩვენება"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Shift ან Symbol კლავიშებიდან გასრიალებისას ვიზუალური მინიშნების ჩვენება"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"ამომხტ.კლავიშის დაყოვნება"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"არ დაყოვნდეს"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ნაგულისხმევი"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>მწმ"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"სისტემის ნაგულისხმევი"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"კონტაქტის სახელების შეთავაზება"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"კონტაქტებიდან სახელების გამოყენება შეთავაზებებისთვის და კორექციისთვის"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"წერტილი ორმაგი შორისით"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"შორისზე ორჯერ შეხება დაწერს წერტილს და შორისის სიმბოლოს"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"ავტო-კაპიტალიზაცია"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"ყოველი წინადადების პირველი სიტყვის კაპიტალიზაცია"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"პერსონალური ლექსიკონი"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"დამატებითი ლექსიკონები"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"მთავარი ლექსიკონი"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"კორექციის შეთავაზებების ჩვენება"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"წერისას შეთავაზებული სიტყვების ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ყოველთვის ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"პორტრეტის რეჟიმში ჩვენება"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ყოველთვის დამალვა"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"შეურაცხმყოფელი სიტყვების დაბლოკვა"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"არ მოხდეს პოტენციურად შეურაცხმყოფელი სიტყვების შეთავაზება"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"ავტოკორექცია"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"შორისი და პუნქტუაცია ავტომატურად ასწორებს არასწორად აკრეფილ სიტყვებს"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"გამორთულია"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"მოკრძალებული"</string>
+    <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"აგრესიული"</string>
+    <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"ძალიან აგრესიული"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"შემდეგი სიტყვის შეთავაზებები"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"შეთავაზებებისას წინა სიტყვის გამოყენება"</string>
+    <string name="gesture_input" msgid="826951152254563827">"ჟესტებით წერის ჩართვა"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"სიტყვის შეყვანა ასო-ნიშებზე გასრიალებით"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"ჟესტიკულაციის კუდის ჩვენება"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"დინამიურად მოლივლივე გადახედვა"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"ჟესტიკულაციისას შეთავაზებული სიტყვის ნახვა"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : შეინახა"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"წასვლ"</string>
+    <string name="label_next_key" msgid="362972844525672568">"შემდ."</string>
     <string name="label_previous_key" msgid="1211868118071386787">"წინა"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"შესრულებულია"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"გაგზავნა"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for send_feedback (1780431884109392046) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- 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) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- 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 />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
-    <skip />
-    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
-    <skip />
-    <!-- no translation found for setup_start_action (8936036460897347708) -->
-    <skip />
-    <!-- no translation found for setup_next_action (371821437915144603) -->
-    <skip />
-    <!-- no translation found for setup_steps_title (6400373034871816182) -->
-    <skip />
-    <!-- no translation found for setup_step1_title (3147967630253462315) -->
-    <skip />
-    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
-    <skip />
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
-    <!-- no translation found for setup_step1_action (4366513534999901728) -->
-    <skip />
-    <!-- no translation found for setup_step2_title (6860725447906690594) -->
-    <skip />
-    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
-    <skip />
-    <!-- no translation found for setup_step2_action (1660330307159824337) -->
-    <skip />
-    <!-- no translation found for setup_step3_title (3154757183631490281) -->
-    <skip />
-    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
-    <skip />
-    <!-- no translation found for setup_step3_action (600879797256942259) -->
-    <skip />
-    <!-- no translation found for setup_finish_action (276559243409465389) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
-    <skip />
-    <!-- no translation found for app_name (6320102637491234792) -->
-    <skip />
-    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
-    <skip />
-    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
-    <skip />
-    <!-- no translation found for download_description (6014835283119198591) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
-    <skip />
-    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
-    <skip />
-    <!-- no translation found for user_dictionaries (3582332055892252845) -->
-    <skip />
-    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
-    <skip />
-    <!-- no translation found for dictionary_available (4728975345815214218) -->
-    <skip />
-    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
-    <skip />
-    <!-- no translation found for dictionary_installed (8081558343559342962) -->
-    <skip />
-    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
-    <skip />
-    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
-    <skip />
-    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
-    <skip />
-    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
-    <skip />
-    <!-- no translation found for last_update (730467549913588780) -->
-    <skip />
-    <!-- no translation found for message_updating (4457761393932375219) -->
-    <skip />
-    <!-- no translation found for message_loading (8689096636874758814) -->
-    <skip />
-    <!-- no translation found for main_dict_description (3072821352793492143) -->
-    <skip />
-    <!-- no translation found for cancel (6830980399865683324) -->
-    <skip />
-    <!-- no translation found for install_dict (180852772562189365) -->
-    <skip />
-    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
-    <skip />
-    <!-- no translation found for delete_dict (756853268088330054) -->
-    <skip />
-    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
-    <skip />
-    <!-- no translation found for download_over_metered (1643065851159409546) -->
-    <skip />
-    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
-    <skip />
-    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
-    <skip />
-    <!-- no translation found for version_text (2715354215568469385) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
+    <string name="label_done_key" msgid="2441578748772529288">"დასრულდა"</string>
+    <string name="label_send_key" msgid="2815056534433717444">"გაგზ."</string>
+    <string name="label_pause_key" msgid="181098308428035340">"პაუზა"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"მოცდა"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"შეაერთეთ ყურსაცვამი, რათა მოისმინოთ აკრეფილი პაროლის კლავიშების სახელები."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"მიმდინარე ტექსტი არის %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"ტექსტ არ შეყვანილა"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"კლავიატურის კოდი %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Shift-"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ჩართულია (შეეხეთ გამოსართავად)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"ჩართულია Caps (შეეხეთ გამოსართავად)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"სიმბოლოები"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"ასოები"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"ნომრები"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"პარამეტრები"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"შორისი"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"ხმოვანი შეყვანა"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"მომღიმარი სახე"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"ძიება"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"წერტილი"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"ენის გადართვა"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"შემდეგი"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"წინა"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ჩართულია"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock ჩართულია"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift გამორთულია"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"სიმბოლოების რეჟიმი"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"ასოების რეჟიმი"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"ტელეფონის რეჟიმი"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"ტელეფონის სიმბოლოების რეჟიმი"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"კლავიატურა დამალულია"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"ნაჩვენებია <xliff:g id="MODE">%s</xliff:g> კლავიატურა"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"თარიღი"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"თარიღი და დრო"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"ელფოსტა"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"შეტყობინებები"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"რიცხვები"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"ტელეფონი"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"ტექსტი"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"დრო"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"ხმოვანი შეყვანის კლავიში"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"მთავარ კლავიატურაზე"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"სიმბოლოთა კლავიატურაზე"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"გამორთულია"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"მიკროფონი მთავარ კლავიატურაზე"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"მიკროფონი სიმბოლოთა კლავიატურაზე"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ხმოვანი შეყვანა გამორთულია"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"შეყვანის მეთოდების კონფიგურაცია"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"შეყვანის ენები"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"უკუკავშირი"</string>
+    <string name="select_language" msgid="3693815588777926848">"შეყვანის ენები"</string>
+    <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="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>
+    <string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ინგლისური (გაერთ. სამ.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ინგლისური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ესპანური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_no_language" msgid="141420857808801746">"ენის გარეშე"</string>
+    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ენის გარეშე (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ენის გარეშე (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ენის გარეშე (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ენის გარეშე (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ენის გარეშე (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ენის გარეშე (PC)"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"შეყვანის სტილების კონფიგურაცია"</string>
+    <string name="add_style" msgid="6163126614514489951">"სტილის დამატება"</string>
+    <string name="add" msgid="8299699805688017798">"დამატება"</string>
+    <string name="remove" msgid="4486081658752944606">"ამოშლა"</string>
+    <string name="save" msgid="7646738597196767214">"შენახვა"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"ენა"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"განლაგება"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"თქვენი მორგებული შეყვანის სტილი გამოყენებამდე უნდა გაააქტიუროთ. გსურთ მისი აქტივაცია ახლა?"</string>
+    <string name="enable" msgid="5031294444630523247">"ჩართვა"</string>
+    <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="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>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ნამდვილად გსურთ ამ ფაილის <xliff:g id="LOCALE_NAME">%s</xliff:g>-ისთვის ინსტალაცია?"</string>
+    <string name="error" msgid="8940763624668513648">"წარმოიშვა შეცდომა"</string>
+    <string name="button_default" msgid="3988017840431881491">"ნაგულისხმევი"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"კეთილი იყოს თქვენი მობრძანება <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ში"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ჟესტებით წერით"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"დაწყება"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"შემდეგი საფეხური"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"მიმდინარეობს <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის დაყენება"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის ჩართვა"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"გთხოვთ მონიშნოთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენი ენის და შეყვანის პარამეტრებში. ეს უფლებას მიცემს მას გაეშვას თქვენს მოწყობილობაზე."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> უკვე გააქტიურებულია თქვენი ენის შეყვანის პარამეტრებში, ასე რომ ეს საფეხური დასრულებულია. გადადით შემდეგ საფეხურზე!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"პარამეტრებში გააქტიურება"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"გადართეთ <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ზე"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"შემდეგ, აირჩიეთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენს აქტიურ შეყვანის მეთოდად."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"შეყვანის მეთოდების გადართვა"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"გილოცავთ, პრცესი დასრულდა!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"ამიერიდან შეძლებთ ყველა სასურველ აპში <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ით წერას."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"დამატებითი ენების კონფიგურაცია"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"დასრულებული"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"აპის ხატულის ჩვენება"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"გაშვების ხატულის ჩვენება გამშვებში"</string>
+    <string name="app_name" msgid="6320102637491234792">"ლექსიკონის პროვაიდერი"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"ლექსიკონის პროვაიდერი"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"ლექსიკონს სერვისი"</string>
+    <string name="download_description" msgid="6014835283119198591">"ლექსიკონის განახლების მონაცემები"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"დამატებითი ლექსიკონები"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"ლექსიკონების პარამეტრები"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"მომხმარებლის ლექსიკონები"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"მომხარებლის ლექსიკონი"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"მიმდინარეობს ჩამოტვირთვა"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"დაყენებულია"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"დაყენებულია, გაუქმებულია"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"ლექსიკონის სერვისთან დაკავშირებით პრობლემა წარმოიშვა"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"ლექსიკონები მიუწვდომელია"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"განახლება"</string>
+    <string name="last_update" msgid="730467549913588780">"ბოლო განახლება"</string>
+    <string name="message_updating" msgid="4457761393932375219">"მიმდინარეობს განახლებების შემოწმება"</string>
+    <string name="message_loading" msgid="8689096636874758814">"იტვირთება..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"მთავარი ლექსიკონი"</string>
+    <string name="cancel" msgid="6830980399865683324">"გაუქმება"</string>
+    <string name="install_dict" msgid="180852772562189365">"ინსტალაცია"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"გაუქმება"</string>
+    <string name="delete_dict" msgid="756853268088330054">"წაშლა"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"თქვენს მიერ მობილურ მოწყობილობაზე არჩეული ენისათვის ხელმისაწვდომია ლექსიკონი.&lt;br/&gt; გირჩევთ, &lt;b&gt;ჩამოტვირთოთ&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ლექსიკონი, რათა გაიმარტივოთ ტექსტის შეყვანა.&lt;br/&gt; &lt;br/&gt; ჩამოტვირთვას შესაძლოა დაჭირდეს ერთი ან ორი წუთი 3G სისწრაფეზე. თუ ულიმიტო არ გაგაჩნიათ &lt;b&gt; მობილური ინტერნეტის ტარიფი&lt;/b&gt;.&lt;br/&amp;gt, შესაძლოა ჩამოტვირთვა დამატებით გადასახადებთან იყოს დაკავშირებული; თუ არ ხართ დარწმუნებული მობილური ინტერნეტის აქტიური ტარიფის შესახებ, გირჩევთ იპოვოთ Wi-Fi კავშირი და ავტომატურად დაიწყოთ ჩამოტვირთვა.&lt;br/&gt; &lt;br/&gt; რჩევა: ლექსიკონების ჩამოტვირთვა და ამოშლა შესაძლებელია სექციიდან &lt;b&gt;ენა და შეყვანა&lt;/b&gt; სექციიდან, თქვენი მობილური მოწყობილობის &lt;b&gt;პარამეტრების&lt;/b&gt; მენიუში."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"ახლა ჩამოტვირთვა (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>მბაიტი)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi კავშირზე ჩამოტვირთვა"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>-ისთვის ხელმისაწვდომია ლექსიკონი"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"დააჭირეთ განხილვას და ჩამოტვირთეთ"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ჩამოტვირთვა: <xliff:g id="LANGUAGE">%1$s</xliff:g>-ის შემოთავაზებები მალე მომზადდება."</string>
+    <string name="version_text" msgid="2715354215568469385">"ვერსია <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"დამატება"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"ლექსიკონში დამატება"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ფრაზა"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"მეტი ვარიანტები"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ნაკლები ვარიანტები"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"კარგი"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"სიტყვა:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"მალსახმობი:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ენა:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"შიყვანეთ სიტყვა"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"არასავალდებულო მალსახმობი"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"სიტყვის შესწორება"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"რედაქტირება"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"წაშლა"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"თქვენ არ გაქვთ სიტყვები მომხმარებლის ლექსიკონში. დაამატეთ სიტყვები ღილაკ Add (+) შეხებით."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"ყველა ენისთვის"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"სხვა ენები…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"წაშლა"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-ko/dictionary-pack.xml b/java/res/values-ko/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ko/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-ko/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-ko/strings-appname.xml
index f65d45b..a25fdb6 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-ko/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android 키보드(AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android 맞춤법 검사기(AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android 키보드 설정(AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android 맞춤법 검사기 설정(AOSP)"</string>
 </resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 7ea31a3..fbcee7d 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"지연 없음"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"기본값"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"시스템 기본값"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"주소록 이름 활용"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"추천 및 수정에 주소록의 이름 사용"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"더블스페이스 마침표"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"스페이스바를 두 번 탭하면 마침표와 공백 한 개가 삽입됩니다."</string>
     <string name="auto_cap" msgid="1719746674854628252">"자동 대문자화"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"문장의 첫 단어를 대문자로 표시"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"개인 사전"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"사전 추가"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"기본 사전"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"수정 제안 표시"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"항상 표시"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"세로 모드로 표시"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"항상 숨기기"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"불쾌감을 주는 단어 차단"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"불쾌감을 줄 수 있는 단어는 추천하지 않음"</string>
     <string name="auto_correction" msgid="7630720885194996950">"자동 수정"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"스페이스바와 문장부호 키를 사용하면 오타가 자동으로 교정됩니다."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"사용 안함"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"이 파일을 <xliff:g id="LOCALE_NAME">%s</xliff:g>(으)로 설치하시겠습니까?"</string>
     <string name="error" msgid="8940763624668513648">"오류 발생"</string>
     <string name="button_default" msgid="3988017840431881491">"기본값"</string>
-    <string name="language_settings" msgid="1671153053201809031">"언어 및 키보드"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"입력 방법 선택"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>에 오신 것을 환영합니다."</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"제스처 타이핑 사용"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"시작하기"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"다음 단계"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> 설정"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> 사용 설정"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"언어 및 입력 설정에서 \'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\'을(를) 확인하세요. 애플리케이션을 기기에서 실행할 수 있도록 승인합니다."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>은(는) 언어 및 입력 설정에서 이미 사용하도록 설정되어 있으므로 이 단계는 완료되었습니다. 다음 단계로 이동하세요."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"\'설정\'에서 사용 설정"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>(으)로 전환"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"그런 다음, 텍스트 입력 방법으로 \'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\'을(를) 선택합니다."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"입력 방법 전환"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"모든 설정이 완료되었습니다."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"이제 즐겨찾는 앱을 <xliff:g id="APPLICATION_NAME">%s</xliff:g>에 입력할 수 있습니다."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"추가 언어 설정"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"완료됨"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"앱 아이콘 표시"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"실행기에 애플리케이션 아이콘 표시"</string>
     <string name="app_name" msgid="6320102637491234792">"사전 제공업체"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"사전 제공업체"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"사전 서비스"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> 사전을 사용할 수 있습니다."</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"검토하고 다운로드하려면 누르세요."</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"다운로드 중: <xliff:g id="LANGUAGE">%1$s</xliff:g>에 대한 추천항목이 곧 준비됩니다."</string>
+    <string name="version_text" msgid="2715354215568469385">"버전 <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"추가"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"사전에 추가"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"구문"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"옵션 더보기"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"옵션 숨기기"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"확인"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"단어:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"단축키:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"언어:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"단어 입력"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"선택적 단축키"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"단어 수정"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"수정"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"삭제"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"사용자 사전에 단어가 없습니다. 추가(+) 버튼을 터치하여 단어를 추가할 수 있습니다."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"모든 언어"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"더보기…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"삭제"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-lt/dictionary-pack.xml b/java/res/values-lt/strings-appname.xml
similarity index 61%
rename from java/res/values-lt/dictionary-pack.xml
rename to java/res/values-lt/strings-appname.xml
index f65d45b..2b9d6ea 100644
--- a/java/res/values-lt/dictionary-pack.xml
+++ b/java/res/values-lt/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"„Android“ klaviatūra (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"„Android“ rašybos tikrinimo programa (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"„Android“ klaviatūros nustatymai (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"„Android“ rašybos tikrinimo programos nustatymai (AOSP)"</string>
 </resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 283013c..dd075e7 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Be delsos"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Numatytasis"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Sist. numat. nustat."</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Siūlyti kontaktų vardus"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Siūlant ir taisant naudoti vardus iš „Kontaktų“"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Tšk. ir tarp. pal. dukart"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dukart palietus tarpo klavišą įterpiamas taškas ir tarpas."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatinis didžiųjų raidžių rašymas"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Kiekvieno sakinio pirmą žodį rašyti iš didžiosios raidės"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Asmeninis žodynas"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Papildomi žodynai"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pagrindinis žodynas"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Rodyti taisymo pasiūlymus"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visada rodyti"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Rodyti portreto režimu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Visada slėpti"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokuoti įžeidžiančius žodžius"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Nesiūlyti galimai įžeidžiančių žodžių"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatinis taisymas"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Tarpo kl. ir skyr. ženkl. aut. išt. neteis. įv. žodž."</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Išjungta"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Ar tikrai įdiegti šį failą <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Įvyko klaida"</string>
     <string name="button_default" msgid="3988017840431881491">"Numatytieji"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Kalba ir įvestis"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Pasirinkite įvesties metodą"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Sveiki! Tai „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"naudojant įvestį gestais"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Pradėti"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Kitas veiksmas"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"„<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ sąranka"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Įgalinkite „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Skiltyje „Kalbos ir įvesties nustatymai“ žr. „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ (progr. bus įgal. veikti įr.)."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"„<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ jau įgalinta jūsų „Kalbos ir įvesties nustatymuose“, todėl šis veiksmas yra atliktas. Galite pereiti prie kito!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Įgalinti nustatymuose"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Perjungimas į „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Toliau pasirinkite „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ kaip aktyvų teksto įvesties metodą."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Perjungti įvesties metodus"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Sveikiname, viską nustatėte!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Dabar galite įvesti visas mėgstamas programas naudodami „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigūruokite papildomas kalbas"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Baigta"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Rodyti programos piktogramą"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Pateikti programos piktogramą paleidimo priemonėje"</string>
     <string name="app_name" msgid="6320102637491234792">"Žodyno teikėjas"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Žodyno teikėjas"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Žodyno paslauga"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Galimas <xliff:g id="LANGUAGE">%1$s</xliff:g> žodynas"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Paspauskite, kad peržiūrėtumėte ir atsisiųstumėte"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Atsisiunčiama. Netrukus bus galimi <xliff:g id="LANGUAGE">%1$s</xliff:g> pasiūlymai."</string>
+    <string name="version_text" msgid="2715354215568469385">"<xliff:g id="VERSION_NUMBER">%1$s</xliff:g> versija"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Pridėti"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Pridėti prie žodyno"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frazė"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Daugiau parink."</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Mažiau parink."</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Gerai"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Žodis:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Spartusis klavišas:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Kalba:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Įveskite žodį"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Pasirenkamasis spartusis klavišas"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Redaguoti žodį"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Redaguoti"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Ištrinti"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Neturite jokių žodžių naudotojo žodyne. Žodį galite pridėti paliesdami mygtuką „Pridėti“ (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Visos kalbos"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Daugiau kalbų..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Ištrinti"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" AĄBCČDEĘĖFGHIĮYJKLMNOPRSŠTUŲŪVZŽ"</string>
 </resources>
diff --git a/java/res/values-lv/dictionary-pack.xml b/java/res/values-lv/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-lv/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-lv/strings-appname.xml
similarity index 61%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-lv/strings-appname.xml
index f65d45b..65b63db 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-lv/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android tastatūra (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android pareizrakstības pārbaudītājs (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android tastatūras iestatījumi (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android pareizrakstības pārbaudītāja iestatījumi (AOSP)"</string>
 </resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 84b11f6..cdf561f 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez aizkaves"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Noklusējums"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Sistēmas noklusējums"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Ieteikt kontaktp. vārdus"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Izmantot kontaktpersonu vārdus kā ieteikumus un labojumus"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dubultpiesk. = punkts"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Divreiz pieskaroties atst. taustiņam, ievada punktu un atstarpi."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automātiska lielo burtu lietošana"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Katra teikuma pirmo vārdu rakstīt ar lielo burtu."</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personiskā vārdnīca"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Papildinājumu vārdnīcas"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Galvenā vārdnīca"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Rādīt labojumu ieteikumus"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vienmēr rādīt"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Rādīt portreta režīmā"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vienmēr slēpt"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloķēt aizvainojošus vārdus"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Neiesakiet vārdus, kas varētu būt aizvainojoši."</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automātiska labošana"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Atstarpes taustiņš un interpunkcija; automātiska kļūdainu vārdu labošana"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izslēgta"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vai instalēt šo failu šādai valodai: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Radās kļūda"</string>
     <string name="button_default" msgid="3988017840431881491">"Noklusējums"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Valoda un ievade"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Ievades metodes izvēle"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Laipni lūdzam pakalpojumā <xliff:g id="APPLICATION_NAME">%s</xliff:g>,"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"kurā varat izmantot ievadi ar žestiem"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Sākt darbu"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Nākamā darbība"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Lietojumprogrammas <xliff:g id="APPLICATION_NAME">%s</xliff:g> iestatīšana"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Lietojumprogrammas <xliff:g id="APPLICATION_NAME">%s</xliff:g> iespējošana"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Valodas un ievades iestatījumos atzīmējiet “<xliff:g id="APPLICATION_NAME">%s</xliff:g>”, autorizējot tās palaišanu ierīcē."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Tā kā lietojumprogramma <xliff:g id="APPLICATION_NAME">%s</xliff:g> jau ir iespējota valodas un iesūtnes iestatījumos, šī darbība ir pabeigta. Veiciet nākamo darbību!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Iespējot iestatījumos"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Pārslēgšanās uz lietojumprogrammu <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Pēc tam atlasiet “<xliff:g id="APPLICATION_NAME">%s</xliff:g>” kā aktīvo ievades metodi."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Pārslēgt ievades metodes"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gatavs!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Izmantojot lietojumprogrammu <xliff:g id="APPLICATION_NAME">%s</xliff:g>, varat rakstīt visās iecienītākajās lietotnēs."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurēt papildu valodas"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Pabeigts"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Rādīt lietotnes ikonu"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Rādīt palaidēja ekrānā lietojumprogrammas ikonu"</string>
     <string name="app_name" msgid="6320102637491234792">"Vārdnīcas nodrošinātājs"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Vārdnīcas nodrošinātājs"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Vārdnīcas pakalpojums"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Ir pieejama vārdnīca šādai valodai: <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Nospiediet, lai pārskatītu un lejupielādētu"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Notiek lejupielāde. Drīz būs pieejami ieteikumi šādai valodai: <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versija <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Pievienot"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Pievienot vārdnīcai"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frāze"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Vairāk opciju"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Mazāk opciju"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Labi"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Vārds:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Saīsne:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Valoda:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Ierakstiet vārdu."</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Izvēles saīsne"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Vārda rediģēšana"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Rediģēt"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Dzēst"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Lietotāja vārdnīcā nav neviena vārda. Lai pievienotu vārdu, pieskarieties pogai Pievienot (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Visās valodās"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Citas valodas..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Dzēst"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-mk/dictionary-pack.xml b/java/res/values-mk/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-mk/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml
deleted file mode 100644
index 0352fa0..0000000
--- a/java/res/values-mk/strings.xml
+++ /dev/null
@@ -1,454 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, 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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Оди"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Следно"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Претходно"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Испрати"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for send_feedback (1780431884109392046) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- 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) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- 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 />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
-    <skip />
-    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
-    <skip />
-    <!-- no translation found for setup_start_action (8936036460897347708) -->
-    <skip />
-    <!-- no translation found for setup_next_action (371821437915144603) -->
-    <skip />
-    <!-- no translation found for setup_steps_title (6400373034871816182) -->
-    <skip />
-    <!-- no translation found for setup_step1_title (3147967630253462315) -->
-    <skip />
-    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
-    <skip />
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
-    <!-- no translation found for setup_step1_action (4366513534999901728) -->
-    <skip />
-    <!-- no translation found for setup_step2_title (6860725447906690594) -->
-    <skip />
-    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
-    <skip />
-    <!-- no translation found for setup_step2_action (1660330307159824337) -->
-    <skip />
-    <!-- no translation found for setup_step3_title (3154757183631490281) -->
-    <skip />
-    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
-    <skip />
-    <!-- no translation found for setup_step3_action (600879797256942259) -->
-    <skip />
-    <!-- no translation found for setup_finish_action (276559243409465389) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
-    <skip />
-    <!-- no translation found for app_name (6320102637491234792) -->
-    <skip />
-    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
-    <skip />
-    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
-    <skip />
-    <!-- no translation found for download_description (6014835283119198591) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
-    <skip />
-    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
-    <skip />
-    <!-- no translation found for user_dictionaries (3582332055892252845) -->
-    <skip />
-    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
-    <skip />
-    <!-- no translation found for dictionary_available (4728975345815214218) -->
-    <skip />
-    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
-    <skip />
-    <!-- no translation found for dictionary_installed (8081558343559342962) -->
-    <skip />
-    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
-    <skip />
-    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
-    <skip />
-    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
-    <skip />
-    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
-    <skip />
-    <!-- no translation found for last_update (730467549913588780) -->
-    <skip />
-    <!-- no translation found for message_updating (4457761393932375219) -->
-    <skip />
-    <!-- no translation found for message_loading (8689096636874758814) -->
-    <skip />
-    <!-- no translation found for main_dict_description (3072821352793492143) -->
-    <skip />
-    <!-- no translation found for cancel (6830980399865683324) -->
-    <skip />
-    <!-- no translation found for install_dict (180852772562189365) -->
-    <skip />
-    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
-    <skip />
-    <!-- no translation found for delete_dict (756853268088330054) -->
-    <skip />
-    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
-    <skip />
-    <!-- no translation found for download_over_metered (1643065851159409546) -->
-    <skip />
-    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
-    <skip />
-    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
-    <skip />
-    <!-- no translation found for version_text (2715354215568469385) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
-</resources>
diff --git a/java/res/values-mn/dictionary-pack.xml b/java/res/values-mn/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-mn/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml
index 46f9c57..04b5109 100644
--- a/java/res/values-mn/strings.xml
+++ b/java/res/values-mn/strings.xml
@@ -20,435 +20,223 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
-    <skip />
-    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Очих"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Дараагийн"</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>
+    <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"Алдаа шалгагч нь таны харилцагчдын жагсаалтаас ашиглана"</string>
+    <string name="vibrate_on_keypress" msgid="5258079494276955460">"Товч дарахад чичрэх"</string>
+    <string name="sound_on_keypress" msgid="6093592297198243644">"Товч дарахад дуу гаргах"</string>
+    <string name="popup_on_keypress" msgid="123894815723512944">"Товч дарахад попап гарна"</string>
+    <string name="general_category" msgid="1859088467017573195">"Ерөнхий"</string>
+    <string name="correction_category" msgid="2236750915056607613">"Текст залруулалт"</string>
+    <string name="gesture_typing_category" msgid="497263612130532630">"Зангаагаар бичих"</string>
+    <string name="misc_category" msgid="6894192814868233453">"Бусад сонголтууд"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Дэлгэрэнгүй тохиргоо"</string>
+    <string name="advanced_settings_summary" msgid="4487980456152830271">"Экспертүүдэд зориулсан тохиргоо"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Оруулах өөр арга руу шилжүүлэх"</string>
+    <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Хэл солих түлхүүрт өөр оруулах аргууд мөн багтсан байгаа"</string>
+    <string name="show_language_switch_key" msgid="5915478828318774384">"Хэл солих товч"</string>
+    <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Оруулах хэл олныг идэвхжүүлсэн үед харуулах"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Гулсалт заагчийг харуулах"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Сэлгэх буюу Симбол товчуудаас гулсах үед нүдэнд харагдуулах"</string>
+    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Товчны попап арилах хугацаа"</string>
+    <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Хүлээхгүй"</string>
+    <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Үндсэн"</string>
+    <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>мс"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Системийн үндсэн утга"</string>
+    <string name="use_contacts_dict" msgid="4435317977804180815">"Харилцагчдын нэрс санал болгох"</string>
+    <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Санал болгох, залруулахда Харилцагчдын нэрсээс ашиглах"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Давхар зайтай цэг"</string>
+    <string name="use_double_space_period_summary" msgid="6532892187247952799">"Ардаа зайтай цэг оруулахын тулд Зай авах дээр давхар товшино уу"</string>
+    <string name="auto_cap" msgid="1719746674854628252">"Автоматаар томруулах"</string>
+    <string name="auto_cap_summary" msgid="7934452761022946874">"Өгүүлбэр бүрийн эхний үгийн эхний үсгийг томруулах"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Хувийн толь бичиг"</string>
+    <string name="configure_dictionaries_title" msgid="4238652338556902049">"Нэмэлт толь бичгүүд"</string>
+    <string name="main_dictionary" msgid="4798763781818361168">"Үндсэн толь бичиг"</string>
+    <string name="prefs_show_suggestions" msgid="8026799663445531637">"Залруулах санал болголтуудыг харуулах"</string>
+    <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"Бичих явцад санал болгосон үгсийг харуулах"</string>
+    <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Байнга харуулах"</string>
+    <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Босоо горимд харуулах"</string>
+    <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Байнга нуух"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Доромжилсон үгсийг хаах"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Доромжилсон үгсийг санал болгохгүй байх"</string>
+    <string name="auto_correction" msgid="7630720885194996950">"Авто-залруулга"</string>
+    <string name="auto_correction_summary" msgid="5625751551134658006">"Хоосон зай болон цэг таслал нь буруу бичсэн үгсийг автоматаар залруулна"</string>
+    <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Идэвхгүй"</string>
+    <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Хүлээцтэй"</string>
+    <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Хүчтэй"</string>
+    <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Маш хүчтэй"</string>
+    <string name="bigram_prediction" msgid="1084449187723948550">"Дараагийн-үг санал болгох"</string>
+    <string name="bigram_prediction_summary" msgid="3896362682751109677">"Өмнөх үгийг үг санал болгоход ашиглах"</string>
+    <string name="gesture_input" msgid="826951152254563827">"Зангаагаар бичихийг идэвхжүүлэх"</string>
+    <string name="gesture_input_summary" msgid="9180350639305731231">"Үсгүүд дээр гулсуулах замаар үг оруулах"</string>
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Зангасан мөрийг харуулах"</string>
+    <string name="gesture_floating_preview_text" msgid="4443240334739381053">"Динамик хөвөгчөөр урьдчилан харах"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Зангах явцад санал болгож буй үгийг харах"</string>
+    <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Хадгалагдсан"</string>
+    <string name="label_go_key" msgid="1635148082137219148">"Явах"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Дараах"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"Өмнөх"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Хийгдлээ"</string>
+    <string name="label_done_key" msgid="2441578748772529288">"Дууссан"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Илгээх"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_hidden (8718927835531429807) -->
-    <skip />
-    <!-- no translation found for announce_keyboard_mode (4729081055438508321) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date (3137520166817128102) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_date_time (339593358488851072) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_email (6216248078128294262) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_im (1137405089766557048) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_number (7991623440699957069) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_phone (6851627527401433229) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_text (6479436687899701619) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_time (4381856885582143277) -->
-    <skip />
-    <!-- no translation found for keyboard_mode_url (1519819835514911218) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for send_feedback (1780431884109392046) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- 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) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- 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 />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
-    <skip />
-    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
-    <skip />
-    <!-- no translation found for setup_start_action (8936036460897347708) -->
-    <skip />
-    <!-- no translation found for setup_next_action (371821437915144603) -->
-    <skip />
-    <!-- no translation found for setup_steps_title (6400373034871816182) -->
-    <skip />
-    <!-- no translation found for setup_step1_title (3147967630253462315) -->
-    <skip />
-    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
-    <skip />
-    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
-    <skip />
-    <!-- no translation found for setup_step1_action (4366513534999901728) -->
-    <skip />
-    <!-- no translation found for setup_step2_title (6860725447906690594) -->
-    <skip />
-    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
-    <skip />
-    <!-- no translation found for setup_step2_action (1660330307159824337) -->
-    <skip />
-    <!-- no translation found for setup_step3_title (3154757183631490281) -->
-    <skip />
-    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
-    <skip />
-    <!-- no translation found for setup_step3_action (600879797256942259) -->
-    <skip />
-    <!-- no translation found for setup_finish_action (276559243409465389) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
-    <skip />
-    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
-    <skip />
-    <!-- no translation found for app_name (6320102637491234792) -->
-    <skip />
-    <!-- no translation found for dictionary_provider_name (3027315045397363079) -->
-    <skip />
-    <!-- no translation found for dictionary_service_name (6237472350693511448) -->
-    <skip />
-    <!-- no translation found for download_description (6014835283119198591) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_title (8091417676045693313) -->
-    <skip />
-    <!-- no translation found for dictionary_install_over_metered_network_prompt (3587517870006332980) -->
-    <skip />
-    <!-- no translation found for dictionary_settings_summary (5305694987799824349) -->
-    <skip />
-    <!-- no translation found for user_dictionaries (3582332055892252845) -->
-    <skip />
-    <!-- no translation found for default_user_dict_pref_name (1625055720489280530) -->
-    <skip />
-    <!-- no translation found for dictionary_available (4728975345815214218) -->
-    <skip />
-    <!-- no translation found for dictionary_downloading (2982650524622620983) -->
-    <skip />
-    <!-- no translation found for dictionary_installed (8081558343559342962) -->
-    <skip />
-    <!-- no translation found for dictionary_disabled (8950383219564621762) -->
-    <skip />
-    <!-- no translation found for cannot_connect_to_dict_service (9216933695765732398) -->
-    <skip />
-    <!-- no translation found for no_dictionaries_available (8039920716566132611) -->
-    <skip />
-    <!-- no translation found for check_for_updates_now (8087688440916388581) -->
-    <skip />
-    <!-- no translation found for last_update (730467549913588780) -->
-    <skip />
-    <!-- no translation found for message_updating (4457761393932375219) -->
-    <skip />
-    <!-- no translation found for message_loading (8689096636874758814) -->
-    <skip />
-    <!-- no translation found for main_dict_description (3072821352793492143) -->
-    <skip />
-    <!-- no translation found for cancel (6830980399865683324) -->
-    <skip />
-    <!-- no translation found for install_dict (180852772562189365) -->
-    <skip />
-    <!-- no translation found for cancel_download_dict (7843340278507019303) -->
-    <skip />
-    <!-- no translation found for delete_dict (756853268088330054) -->
-    <skip />
-    <!-- no translation found for should_download_over_metered_prompt (2878629598667658845) -->
-    <skip />
-    <!-- no translation found for download_over_metered (1643065851159409546) -->
-    <skip />
-    <!-- no translation found for do_not_download_over_metered (2176209579313941583) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_title (6514288591959117288) -->
-    <skip />
-    <!-- no translation found for dict_available_notification_description (1075194169443163487) -->
-    <skip />
-    <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
-    <skip />
-    <!-- no translation found for version_text (2715354215568469385) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
-    <skip />
-    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
-    <skip />
-    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
-    <skip />
+    <string name="label_pause_key" msgid="181098308428035340">"Пауз"</string>
+    <string name="label_wait_key" msgid="6402152600878093134">"Хүлээх"</string>
+    <string name="spoken_use_headphones" msgid="896961781287283493">"Нууц үгний товчнуудыг чангаар уншихыг сонсохын тулд чихэвчээ залгана уу."</string>
+    <string name="spoken_current_text_is" msgid="2485723011272583845">"Одоогийн текст %s"</string>
+    <string name="spoken_no_text_entered" msgid="7479685225597344496">"Текст оруулаагүй"</string>
+    <string name="spoken_description_unknown" msgid="3197434010402179157">"Товчийн код %d"</string>
+    <string name="spoken_description_shift" msgid="244197883292549308">"Сэлгэх"</string>
+    <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Сэлгэхийг идэвхжүүлсэн (товшиж идэвхгүйжүүлнэ үү)"</string>
+    <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Томоор бичихийг асаасан (товшиж идэвхгүйжүүлнэ үү)"</string>
+    <string name="spoken_description_delete" msgid="8740376944276199801">"Устгах"</string>
+    <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Симбол"</string>
+    <string name="spoken_description_to_alpha" msgid="23129338819771807">"Үсэгнүүд"</string>
+    <string name="spoken_description_to_numeric" msgid="591752092685161732">"Тоонууд"</string>
+    <string name="spoken_description_settings" msgid="4627462689603838099">"Тохиргоо"</string>
+    <string name="spoken_description_tab" msgid="2667716002663482248">"Таб"</string>
+    <string name="spoken_description_space" msgid="2582521050049860859">"Хоосон зай"</string>
+    <string name="spoken_description_mic" msgid="615536748882611950">"Дуугаар оруулах"</string>
+    <string name="spoken_description_smiley" msgid="2256309826200113918">"Инээсэн царай"</string>
+    <string name="spoken_description_return" msgid="8178083177238315647">"Буцах"</string>
+    <string name="spoken_description_search" msgid="1247236163755920808">"Хайх"</string>
+    <string name="spoken_description_dot" msgid="40711082435231673">"Цэг"</string>
+    <string name="spoken_description_language_switch" msgid="5507091328222331316">"Хэл солих"</string>
+    <string name="spoken_description_action_next" msgid="8636078276664150324">"Дараах"</string>
+    <string name="spoken_description_action_previous" msgid="800872415009336208">"Өмнөх"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Сэлгэхийг идэвхжүүлсэн"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Томоор бичихийг идэвхжүүлсэн"</string>
+    <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Сэлгэхийг идэвхжүүлээгүй"</string>
+    <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"Симбол төлөв"</string>
+    <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Үсэгнүүд төлөв"</string>
+    <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Утасны төлөв"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Утасны символ төлөв"</string>
+    <string name="announce_keyboard_hidden" msgid="8718927835531429807">"Гарыг нуусан"</string>
+    <string name="announce_keyboard_mode" msgid="4729081055438508321">"<xliff:g id="MODE">%s</xliff:g> гарыг харуулж байна"</string>
+    <string name="keyboard_mode_date" msgid="3137520166817128102">"огноо"</string>
+    <string name="keyboard_mode_date_time" msgid="339593358488851072">"огноо болон цаг"</string>
+    <string name="keyboard_mode_email" msgid="6216248078128294262">"имэйл"</string>
+    <string name="keyboard_mode_im" msgid="1137405089766557048">"зурвас"</string>
+    <string name="keyboard_mode_number" msgid="7991623440699957069">"дугаар"</string>
+    <string name="keyboard_mode_phone" msgid="6851627527401433229">"утас"</string>
+    <string name="keyboard_mode_text" msgid="6479436687899701619">"текст"</string>
+    <string name="keyboard_mode_time" msgid="4381856885582143277">"цаг"</string>
+    <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string>
+    <string name="voice_input" msgid="3583258583521397548">"Дуун оруулгын товч"</string>
+    <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Үндсэн гар дээр"</string>
+    <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Симбол гар дээр"</string>
+    <string name="voice_input_modes_off" msgid="3745699748218082014">"Хаах"</string>
+    <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"Мик үндсэн гар дээр"</string>
+    <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Мик симбол гар дээр"</string>
+    <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Дуун оруулах идэвхгүйжсэн"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Оруулах аргуудын тохиргоо"</string>
+    <string name="language_selection_title" msgid="1651299598555326750">"Оруулах хэл"</string>
+    <string name="send_feedback" msgid="1780431884109392046">"Санал хүсэл илгээх"</string>
+    <string name="select_language" msgid="3693815588777926848">"Оруулах хэл"</string>
+    <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="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>
+    <string name="subtype_es_US" msgid="5583145191430180200">"Испани (АНУ)"</string>
+    <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Англи (ИБ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Англи (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Испани (АНУ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_no_language" msgid="141420857808801746">"Хэл байхгүй"</string>
+    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Хэл байхгүй (QWERTY)"</string>
+    <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Хэл байхгүй (QWERTZ)"</string>
+    <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Хэл байхгүй (AZERTY)"</string>
+    <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Хэл байхгүй (Dvorak)"</string>
+    <string name="subtype_no_language_colemak" msgid="4205992994906097244">"Хэл байхгүй (Colemak)"</string>
+    <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"Хэл байхгүй (PC)"</string>
+    <string name="custom_input_styles_title" msgid="8429952441821251512">"Өөрийн оруулах загвар"</string>
+    <string name="add_style" msgid="6163126614514489951">"Загвар нэмэх"</string>
+    <string name="add" msgid="8299699805688017798">"Нэмэх"</string>
+    <string name="remove" msgid="4486081658752944606">"Устгах"</string>
+    <string name="save" msgid="7646738597196767214">"Хадгалах"</string>
+    <string name="subtype_locale" msgid="8576443440738143764">"Хэл"</string>
+    <string name="keyboard_layout_set" msgid="4309233698194565609">"Байршил"</string>
+    <string name="custom_input_style_note_message" msgid="8826731320846363423">"Та өөрийн оруулах загварыг ашиглахаас өмнө идэвхжүүлэх шаардлагатай. Одоо идэвхжүүлэх үү?"</string>
+    <string name="enable" msgid="5031294444630523247">"Идэвхжүүлэх"</string>
+    <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="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>
+    <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g>-д зориулсан энэ файлыг үнэхээр суулгах уу?"</string>
+    <string name="error" msgid="8940763624668513648">"Алдаа гарсан"</string>
+    <string name="button_default" msgid="3988017840431881491">"Үндсэн"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Та <xliff:g id="APPLICATION_NAME">%s</xliff:g>-д тавтай морилно уу"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"Зангаагаар бичихээр"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Эхлүүлэх"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Дараагийн алхам"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-г тохируулж байна"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-г идэвхжүүлэх"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Өөрийн Хэл &amp; оруулах тохиргоон дотроос \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\"-г сонгоно уу. Ингэснээр таны төхөөрөмж дээр ажиллах зөвшөөрлийг өгөх болно."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> таны Хэл &amp;amp оруулах тохиргоонд аль хэдийн идэвхжүүлсэн байгаа учир энэ алхам хийгдсэн. Дараагийн алхам руу!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Тохиргоо дотроос идэвхжүүлэх"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> рүү шилжих"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Дараа нь \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\"-г өөрийн идэвхтэй текст-оруулах аргаар сонгоно уу."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Оруулах аргыг солих"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Баяр хүргэе, та бүгдийг нь тохируулчихлаа!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Та одоо өөрийн дуртай апп-ууд дотроо <xliff:g id="APPLICATION_NAME">%s</xliff:g> ашиглан бичих болохоор боллоо."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Нэмэлт хэлнүүдийг тохируулах"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Дууссан"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Апп дүрсийг харуулах"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Эхлүүлэгч дээр аппликешний дүрсийг харуулах"</string>
+    <string name="app_name" msgid="6320102637491234792">"Толь бичгээг хангагч"</string>
+    <string name="dictionary_provider_name" msgid="3027315045397363079">"Толь бичгээг хангагч"</string>
+    <string name="dictionary_service_name" msgid="6237472350693511448">"Толь бичгийн үйлчилгээ"</string>
+    <string name="download_description" msgid="6014835283119198591">"Толь бичгийн шинэчлэлтийн мэдээлэл"</string>
+    <string name="dictionary_settings_title" msgid="8091417676045693313">"Нэмэлт толь бичгүүд"</string>
+    <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"Толь бичиг байна"</string>
+    <string name="dictionary_settings_summary" msgid="5305694987799824349">"Толь бичгийн тохиргоо"</string>
+    <string name="user_dictionaries" msgid="3582332055892252845">"Хэрэглэгчийн толь бичиг"</string>
+    <string name="default_user_dict_pref_name" msgid="1625055720489280530">"Хэрэглэгчийн толь"</string>
+    <string name="dictionary_available" msgid="4728975345815214218">"Толь бичиг байна"</string>
+    <string name="dictionary_downloading" msgid="2982650524622620983">"Одоо татаж байна"</string>
+    <string name="dictionary_installed" msgid="8081558343559342962">"Суулгасан"</string>
+    <string name="dictionary_disabled" msgid="8950383219564621762">"Суулгасан, идэвхгүйжүүлсэн"</string>
+    <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"Толь бичгийн үйлчилгээнд холбогдоход алдаа гарлаа"</string>
+    <string name="no_dictionaries_available" msgid="8039920716566132611">"Толь бичиг байхгүй"</string>
+    <string name="check_for_updates_now" msgid="8087688440916388581">"Дахин ачаалах"</string>
+    <string name="last_update" msgid="730467549913588780">"Сүүлд шинэчлэгдсэн"</string>
+    <string name="message_updating" msgid="4457761393932375219">"Шинэчлэлтийг шалгаж байна"</string>
+    <string name="message_loading" msgid="8689096636874758814">"Ачаалж байна..."</string>
+    <string name="main_dict_description" msgid="3072821352793492143">"Үндсэн толь бичиг"</string>
+    <string name="cancel" msgid="6830980399865683324">"Цуцлах"</string>
+    <string name="install_dict" msgid="180852772562189365">"Суулгах"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"Цуцлах"</string>
+    <string name="delete_dict" msgid="756853268088330054">"Устгах"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"Таны мобайль төхөөрөмж дээр сонгосон хэлэнд толь бичиг байна.&lt;br/&gt; Тус  <xliff:g id="LANGUAGE">%1$s</xliff:g> толь бичгийг &lt;b&gt;татаж аван&lt;/b&gt; зөв бичилтээ сайжруулахыг бид зөвлөж байна.&lt;br/&gt; &lt;br/&gt; Татаж авахад 3G сүлжээгээр нэг хоёр минут болно. Танд &lt;b&gt;хязгааргүй дата эрх&lt;/b&gt; байхгүй бол нэмэлт төлбөр гарч болно.&lt;br/&gt; Та дата эрхийнхээ талаар сайн мэдэхгүй байгаа бол Wi-Fi холболттой газар очин автоматаар татаж авахыг зөвлөж байна.&lt;br/&gt; &lt;br/&gt; Зөвлөмж: Та өөрийн мобайль төхөөрөмжийн &lt;b&gt;Тохиргоо&lt;/b&gt; цэсний &lt;b&gt;Хэл &amp; оруулах&lt;/b&gt; руу очиж толь бичиг татаж авах буюу устгаж болно."</string>
+    <string name="download_over_metered" msgid="1643065851159409546">"Одоо татах (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi-р татаж авах"</string>
+    <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> хэлний толь бичигтэй"</string>
+    <string name="dict_available_notification_description" msgid="1075194169443163487">"Шалгах болон татаж авахын тулд дарна уу"</string>
+    <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Татаж байна: <xliff:g id="LANGUAGE">%1$s</xliff:g> хэлний санал болгох үгс удахгүй бэлэн болно."</string>
+    <string name="version_text" msgid="2715354215568469385">"Хувилбар <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Нэмэх"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Толь бичигт нэмэх"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Хэллэг"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Илүү сонголтууд"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Цөөн сонголт"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Тийм"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Үг:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Товчилбор:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Хэл:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Үг оруулна уу"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Зайлшгүй биш товчилбор"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Үг засах"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Засах"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Устгах"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Таны хэрэглэгчийн толинд ямар ч үг алга байна. Нэмэх (+) товчинд хүрэн үг нэмнэ үү."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Бүх хэлэнд"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Өөр хэлүүд…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Устгах"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-ms/dictionary-pack.xml b/java/res/values-ms/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ms/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-ms/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-ms/strings-appname.xml
index f65d45b..76d1d29 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-ms/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Papan Kekunci Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Penyemak Ejaan Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Tetapan Papan Kekunci Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Tetapan Penyemak Ejaan Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 67fdea9..8138663 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Tiada lengah"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Lalai"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Tetapan asal sistem"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Cadangkan nama Kenalan"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Menggunakan nama daripada Kenalan untuk cadangan dan pembetulan"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Titik ruang berganda"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Mengetik 2X pada bar ruang memasukkan titik diikuti dengan ruang"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Huruf besar auto"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Besarkan perkataan pertama setiap ayat"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Kamus peribadi"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Kamus tambahan"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamus utama"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Tunjukkan cadangan pembetulan"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Sentiasa tunjukkan"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Tunjukkan dalam mod potret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sentiasa sembunyikan"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Sekat perkataan yg menyinggung"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Jangan cadangkan perkataan yang boleh menyinggung"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Auto pembetulan"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Bar ruang dan tanda baca secara automatik membetulkan perkataan yang ditaip salah"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Matikan"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Betul-betul pasang fail ini untuk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Berlaku ralat"</string>
     <string name="button_default" msgid="3988017840431881491">"Lalai"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Bahasa &amp; input"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Pilih kaedah input"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Selamat datang ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"dengan Taipan Gerak Isyarat"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Bermula"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Langkah seterusnya"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Menyediakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Dayakan <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sila semak \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" dlm ttpn Bhs &amp; input. Ini mbnarkn apl djlnkn pd pranti anda."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> sudah didayakan dalam tetapan Bahasa &amp; input anda, jadi langkah ini telah selesai. Beralih ke langkah seterusnya!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Dayakan dalam Tetapan"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Beralih ke <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Seterusnya, pilih \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" sebagai kaedah input teks aktif anda."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Tukar kaedah input"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Tahniah, anda sudah sedia!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Kini anda boleh menaip dalam semua apl kegemaran anda dengan <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurasikan bahasa tambahan"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Selesai"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Tunjukkan ikon apl"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Paparkan ikon apl dalam pelancar"</string>
     <string name="app_name" msgid="6320102637491234792">"Pembekal Kamus"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Pembekal Kamus"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Perkhidmatan Kamus"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Kamus tersedia untuk <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Tekan untuk mengulas dan memuat turun"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Memuat turun: cadangan untuk <xliff:g id="LANGUAGE">%1$s</xliff:g> akan sedia tidak lama lagi."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versi <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Tambah"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Tambah ke kamus"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frasa"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Lagi pilihan"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Kurang pilihan"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Perkataan:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Pintasan:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Bahasa:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Taip perkataan"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Pintasan pilihan"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edit perkataan"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Padam"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Anda tidak mempunyai sebarang perkataan dalam kamus pengguna. Tambahkan perkataan dengan menyentuh butang Tambah (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Untuk semua bahasa"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Lebih banyak bahasa..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Padam"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-nb/dictionary-pack.xml b/java/res/values-nb/strings-appname.xml
similarity index 63%
rename from java/res/values-nb/dictionary-pack.xml
rename to java/res/values-nb/strings-appname.xml
index f65d45b..8a71405 100644
--- a/java/res/values-nb/dictionary-pack.xml
+++ b/java/res/values-nb/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-tastatur (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android-stavekontroll (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Instillinger for Android-tastatur (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Innstillinger for Android-stavekontroll (AOSP)"</string>
 </resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index caa98cc..9544108 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"U/ forsinkelse"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Systemstandard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Foreslå kontaktnavn"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Bruk navn fra Kontakter til forslag og korrigeringer"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Punktum ved doble mellomrom"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dobbeltrykk på mellomromstasten for punktum etterfulgt av mellomrom"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Stor forbokstav"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Sett stor bokstav i det første ordet i hver setning"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personlig ordliste"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tilleggsordbøker"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hovedordliste"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Vis rettingsforslag"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vis alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Vis i stående modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Skjul alltid"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokkér støtende ord"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ikke foreslå potensielt støtende ord"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autokorrektur"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Mellomromstast og skilletegn retter automat. feilstavede ord"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vil du virkelig installere denne filen for <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Det oppsto en feil"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Språk og inndata"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Velg inndatametode"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Velkommen til <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"med Ordføring"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Startveiledning"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Neste trinn"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Konfigurerer <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Aktiver <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sjekk <xliff:g id="APPLICATION_NAME">%s</xliff:g> i Språk og inndata-innstillingene dine. Dette tillater appen å kjøre på enheten."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> er allerede aktivert i Språk og inndata-innstillingene dine, så dette trinnet er fullført. Gå til neste trinn!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktiver i Innstillinger"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Bytt til <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Deretter velger du  «<xliff:g id="APPLICATION_NAME">%s</xliff:g>» som den aktive inndatametoden for tekst."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Bytt inndatametode"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gratulerer, du er klar!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nå kan du skrive inn alle favorittappene dine med <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurer flere språk"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Fullført"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Vis app-ikonet"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Vis app-ikonet i appvelgeren"</string>
     <string name="app_name" msgid="6320102637491234792">"Ordlisteleverandør"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Ordlisteleverandør"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Ordlistetjeneste"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"En ordliste er tilgjengelig for <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Trykk for å se gjennom og laste ned"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Laster ned: forslag blir snart tilgjengelige for <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versjon <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Legg til"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Legg til i ordlisten"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Setning"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Flere alt."</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Færre alt."</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Ord:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Snarvei:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Språk:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Skriv inn et ord"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valgfri snarvei"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Rediger ord"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Rediger"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Slett"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Det finnes ingen ord i brukerordlisten. Du kan legge til et ord ved å trykke på Legg til-knappen (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"For alle språk"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Flere språk"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Slett"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ"</string>
 </resources>
diff --git a/java/res/values-nl/dictionary-pack.xml b/java/res/values-nl/strings-appname.xml
similarity index 62%
rename from java/res/values-nl/dictionary-pack.xml
rename to java/res/values-nl/strings-appname.xml
index f65d45b..41f85dc 100644
--- a/java/res/values-nl/dictionary-pack.xml
+++ b/java/res/values-nl/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android-toetsenbord (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Spellingcontrole van Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Instellingen voor Android-toetsenbord (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Instellingen voor spellingcontrole van Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 5480430..08b14d4 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Geen vertraging"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standaard"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Systeemstandaard"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Contactnamen suggereren"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Namen uit Contacten gebruiken voor suggesties en correcties"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dubbeltik is punt, spatie"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dubbeltik op spatiebalk voor een punt gevolgd door een spatie"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-hoofdlettergebruik"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Het eerste woord van elke zin met een hoofdletter schrijven"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Persoonlijk woordenboek"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Woordenboeken toevoegen"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Algemeen woordenboek"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Correctievoorstellen weergeven"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Altijd weergeven"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Weergeven in staande modus"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Altijd verbergen"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Aanstootgevende woorden blokk."</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Geen potentieel aanstootgevende woorden voorstellen"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autocorrectie"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Met spatiebalk en interpunctie worden verkeerd gespelde woorden automatisch gecorrigeerd"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Uitgeschakeld"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Wilt u dit bestand voor <xliff:g id="LOCALE_NAME">%s</xliff:g> echt installeren?"</string>
     <string name="error" msgid="8940763624668513648">"Er is een fout opgetreden"</string>
     <string name="button_default" msgid="3988017840431881491">"Standaard"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Taal en invoer"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Invoermethode selecteren"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Welkom bij <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"met Invoer met bewegingen"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Aan de slag"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Volgende stap"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> instellen"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> inschakelen"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Vink \'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\' aan in \'Instellingen voor taal en invoer\'. De app kan dan worden uitgevoerd op uw apparaat."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> is al ingeschakeld in \'Instellingen voor taal en invoer\', dus deze stap is voltooid. Op naar de volgende!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Inschakelen in \'Instellingen\'"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Overschakelen naar <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Selecteer vervolgens \'<xliff:g id="APPLICATION_NAME">%s</xliff:g>\' als actieve tekstinvoermethode."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Schakelen tussen invoermethoden"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gefeliciteerd, u kunt nu aan de slag."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"U kunt nu in al uw favoriete apps typen met <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Extra talen configureren"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Voltooid"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"App-pictogram weergeven"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"App-pictogram weergeven in het opstartprogramma"</string>
     <string name="app_name" msgid="6320102637491234792">"Woordenboekleverancier"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Woordenboekleverancier"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Woordenboekservice"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Er is een woordenboek beschikbaar voor het <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Druk om te controleren en te downloaden"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Downloaden: suggesties voor het <xliff:g id="LANGUAGE">%1$s</xliff:g> zijn straks beschikbaar."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versie <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Toevoegen"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Toevoegen aan woordenboek"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Zinsdeel"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Meer opties"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Minder opties"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Woord:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Sneltoets:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Taal:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Typ een woord"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Optionele snelkoppeling"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Woord bewerken"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Bewerken"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Verwijderen"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Er staan geen woorden in het gebruikerswoordenboek. U kunt een woord toevoegen door op de knop \'Toevoegen\' (+) te tikken."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Voor alle talen"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Meer talen…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Verwijderen"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-pl/dictionary-pack.xml b/java/res/values-pl/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-pl/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-pl/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-pl/strings-appname.xml
index f65d45b..bcdec24 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-pl/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Klawiatura Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Sprawdzanie pisowni w Androidzie (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Ustawienia klawiatury Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Ustawienia sprawdzania pisowni w Androidzie (AOSP)"</string>
 </resources>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index fc95671..dec06ab 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez opóźnienia"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Wartość domyślna"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Ustawienie domyślne"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Proponuj osoby z kontaktów"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"W propozycjach i poprawkach użyj nazwisk z kontaktów"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Szybka kropka ze spacją"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dwukrotne kliknięcie spacji wstawia kropkę ze spacją"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Wstawiaj wielkie litery"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Zaczynaj każde zdanie wielką literą"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Słownik osobisty"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dodatkowe słowniki"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Słownik główny"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Pokazuj propozycje poprawek"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Zawsze pokazuj"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Pokaż w trybie pionowym"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Zawsze ukrywaj"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokuj obraźliwe słowa"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Nie proponuj słów potencjalnie obraźliwych"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autokorekta"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Spacja i znaki przestankowe poprawiają błędnie wpisane słowa"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Wyłącz"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Czy na pewno zainstalować ten plik dla języka: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Wystąpił błąd"</string>
     <string name="button_default" msgid="3988017840431881491">"Domyślne"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Język, klawiatura, głos"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Wybierz metodę wprowadzania"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Witamy w aplikacji <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"z pisaniem gestami"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Rozpocznij"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Następny krok"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Konfigurowanie aplikacji <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Włącz aplikację <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Zaznacz aplikację „<xliff:g id="APPLICATION_NAME">%s</xliff:g>” w ustawieniach Język, klawiatura i głos. Umożliwi to jej uruchamianie na urządzeniu."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Aplikacja <xliff:g id="APPLICATION_NAME">%s</xliff:g> jest już włączona w Ustawieniach języka i wprowadzania danych. Przejdź do następnego kroku."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Włącz w Ustawieniach"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Przełącz się na aplikację <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Następnie wybierz „<xliff:g id="APPLICATION_NAME">%s</xliff:g>” jako aktywną metodę wprowadzania tekstu."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Przełącz metody wprowadzania"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Gratulacje! Wszystko gotowe."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Teraz możesz pisać we wszystkich swoich ulubionych aplikacjach, używając aplikacji <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Skonfiguruj dodatkowe języki"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Zakończone"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Pokaż ikonę aplikacji"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Wyświetl ikonę aplikacji w programie uruchamiającym"</string>
     <string name="app_name" msgid="6320102637491234792">"Dostawca słownika"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dostawca słownika"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Usługa słownika"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Dostępny jest słownik <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Naciśnij, by sprawdzić i pobrać"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Pobieranie – wkrótce będą dostępne sugestie w tym języku: <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Wersja <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Dodaj"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Dodaj do słownika"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fraza"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Więcej opcji"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Mniej opcji"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Słowo:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Skrót:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Język:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Wpisz słowo"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Opcjonalny skrót"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Edytuj słowo"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Edytuj"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Usuń"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Nie masz żadnych słów w słowniku użytkownika. Aby dodać słowo, kliknij przycisk Dodaj (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Dla wszystkich języków"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Więcej języków…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Usuń"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-pt-rPT/dictionary-pack.xml b/java/res/values-pt-rPT/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-pt-rPT/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-pt-rPT/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-pt-rPT/strings-appname.xml
index f65d45b..368207c 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-pt-rPT/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclado Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Verificador Ortográfico Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Definições do Teclado Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Definições do Verificador Ortográfico Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 479ff94..3c51154 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sem atraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predefinido"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Predef. do sistema"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nomes de Contactos"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizar nomes dos Contactos para sugestões e correções"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Ponto de espaço duplo"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Tocar duas vezes na barra espaço insere ponto seguido de espaço"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Letras maiúsculas automáticas"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Maiúscula no início da frase"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dicionário pessoal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicionários extras"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicionário principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Mostrar sugestões de correcção"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar em modo retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ocultar sempre"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palavras ofensivas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Não sugerir palavras potencialmente ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correção automática"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Correcção automática de palavras mal escritas c/ barra de espaços e pontuação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desligar"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Instalar mesmo este ficheiro para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ocorreu um erro"</string>
     <string name="button_default" msgid="3988017840431881491">"Predefinido"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Idioma e entrada de som"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Escolher o método de entrada"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Bem-vindo(a) a <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"com a Escrita com Gestos"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Começar"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Passo seguinte"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurar <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Ativar <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Marque \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" nas definições de Idioma e introdução p/ autorizar a execução no dispositivo."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> já está ativado nas Definições de idioma e introdução, por isso, este passo está concluído. Passemos ao seguinte!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Ativar nas Definições"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Mudar para <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Em seguida, selecione \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" como o seu método de introdução de texto ativo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Mudar métodos de introdução"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Parabéns, está pronto para começar!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Agora pode escrever em todas as suas aplicações favoritas com <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurar idiomas adicionais"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Concluído"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostrar ícone da aplicação"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostrar ícone da aplicação no iniciador"</string>
     <string name="app_name" msgid="6320102637491234792">"Fornecedor de Dicionário"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Fornecedor de Dicionário"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Serviço de Dicionário"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Está disponível um dicionário para <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Prima para consultar e transferir"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"A transferir: as sugestões para <xliff:g id="LANGUAGE">%1$s</xliff:g> estarão prontas em breve."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versão <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Adicionar"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Adicionar ao dicionário"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Expressão"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Mais opções"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menos opções"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Palavra:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Atalho:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Escreva uma palavra"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Atalho opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editar palavra"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editar"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Eliminar"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Não tem palavras no dicionário do utilizador. Para adicionar uma palavra, toque no botão Adicionar (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Em todos os idiomas"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Mais idiomas..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Eliminar"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-pt/dictionary-pack.xml b/java/res/values-pt/strings-appname.xml
similarity index 62%
rename from java/res/values-pt/dictionary-pack.xml
rename to java/res/values-pt/strings-appname.xml
index f65d45b..7180a0c 100644
--- a/java/res/values-pt/dictionary-pack.xml
+++ b/java/res/values-pt/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Teclado Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Corretor ortográfico do Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Configurações de teclado Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Configurações de corretor ortográfico do Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index dc01c5b..babf4ac 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Sem atraso"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Padrão"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Padrão do sistema"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugerir nomes de contato"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Usar nomes dos Contatos para sugestões e correções"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Duplo espaço p/ ponto"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Toque duplo na barra de espaço insere um ponto seguido de espaço"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Capitaliz. automática"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Iniciar a primeira palavra de cada frase com letra maiúscula"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dicionário pessoal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicionários complementares"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicionário principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Exibir sugestões de correção"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Mostrar sempre"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Mostrar em modo de retrato"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Sempre ocultar"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Bloquear palavras ofensivas"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Não sugerir palavras potencialmente ofensivas"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Correção automática"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"A barra de espaço e a pontuação corrigem automaticamente palavras com erro de digitação"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Desativado"</string>
@@ -137,14 +137,14 @@
     <string name="prefs_enable_log" msgid="6620424505072963557">"Ativar comentário do usuário"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"Ajude a melhorar este editor de método de entrada enviando automaticamente estatísticas de uso e relatórios de falhas."</string>
     <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>
-    <string name="subtype_es_US" msgid="5583145191430180200">"Espanhol (EUA)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"inglês (Reino Unido)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"inglês (EUA)"</string>
+    <string name="subtype_es_US" msgid="5583145191430180200">"espanhol (EUA)"</string>
     <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Inglês (Reino Unido) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Inglês (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
-    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"Espanhol (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
+    <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"espanhol (EUA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string>
     <string name="subtype_no_language" msgid="141420857808801746">"Sem idioma"</string>
-    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"Nenhum idioma (QWERTY)"</string>
+    <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"nenhum idioma (QWERTY)"</string>
     <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"Nenhum idioma (QWERTZ)"</string>
     <string name="subtype_no_language_azerty" msgid="8721460968141187394">"Nenhum idioma (AZERTY)"</string>
     <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"Nenhum idioma (Dvorak)"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Deseja instalar este arquivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ocorreu um erro"</string>
     <string name="button_default" msgid="3988017840431881491">"Padrão"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Idioma e entrada"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Selecione o método de entrada"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Bem-vindo ao <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"com entrada por gestos"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Começar"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Próxima etapa"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurando o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Ative o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Marque \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" em \"Configurações de idioma e entrada\" para autorizar a execução."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> já está ativado em suas configurações de idioma e entrada. Esta etapa está concluída. Vamos avançar para a próxima!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Ativar em \"Configurações\""</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Abra o <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Em seguida, selecione \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" como o método de entrada de texto ativo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Alternar métodos de entrada"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Parabéns, você terminou!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Agora você pode digitar em todos os seus aplicativos favoritos com o <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurar idiomas adicionais"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Concluído"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Mostrar ícone do aplicativo"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Mostrar ícone do aplicativo no iniciador"</string>
     <string name="app_name" msgid="6320102637491234792">"Provedor de dicionário"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Provedor de dicionário"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Serviço de dicionário"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Há um dicionário disponível para <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pressione para consultar e fazer o download"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Download em andamento: as sugestões para <xliff:g id="LANGUAGE">%1$s</xliff:g> estarão prontas em breve."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versão <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Adicionar"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Adicionar ao dicionário"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Frase"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Mais opções"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menos opções"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Ok"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Palavra:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Atalho:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Idioma:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Digite uma palavra"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Atalho opcional"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editar palavra"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editar"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Excluir"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Você não possui palavras no dicionário do usuário. Adicione uma palavra tocando no botão \"Adicionar\" ou no símbolo \"+\"."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Para todos os idiomas"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Mais idiomas..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Excluir"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-rm/dictionary-pack.xml b/java/res/values-rm/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-rm/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 15cd327..9eee1bb 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -20,14 +20,6 @@
 
 <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 />
     <!-- no translation found for english_ime_input_options (3909945612939668554) -->
     <skip />
     <!-- no translation found for english_ime_research_log (8492602295696577851) -->
@@ -71,6 +63,8 @@
     <skip />
     <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
     <skip />
+    <!-- no translation found for settings_system_default (6268225104743331821) -->
+    <skip />
     <!-- no translation found for use_contacts_dict (4435317977804180815) -->
     <skip />
     <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
@@ -82,6 +76,8 @@
     <string name="auto_cap" msgid="1719746674854628252">"Maiusclas automaticas"</string>
     <!-- no translation found for auto_cap_summary (7934452761022946874) -->
     <skip />
+    <!-- no translation found for edit_personal_dictionary (3996910038952940420) -->
+    <skip />
     <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
     <skip />
     <!-- no translation found for main_dictionary (4798763781818361168) -->
@@ -96,6 +92,10 @@
     <skip />
     <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
     <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_title (5078480071057408934) -->
+    <skip />
+    <!-- no translation found for prefs_block_potentially_offensive_summary (2371835479734991364) -->
+    <skip />
     <!-- no translation found for auto_correction (7630720885194996950) -->
     <skip />
     <!-- no translation found for auto_correction_summary (5625751551134658006) -->
@@ -310,9 +310,41 @@
     <skip />
     <!-- no translation found for button_default (3988017840431881491) -->
     <skip />
-    <!-- no translation found for language_settings (1671153053201809031) -->
+    <!-- no translation found for setup_welcome_title (6112821709832031715) -->
     <skip />
-    <!-- no translation found for select_input_method (4301602374609275003) -->
+    <!-- no translation found for setup_welcome_additional_description (8150252008545768953) -->
+    <skip />
+    <!-- no translation found for setup_start_action (8936036460897347708) -->
+    <skip />
+    <!-- no translation found for setup_next_action (371821437915144603) -->
+    <skip />
+    <!-- no translation found for setup_steps_title (6400373034871816182) -->
+    <skip />
+    <!-- no translation found for setup_step1_title (3147967630253462315) -->
+    <skip />
+    <!-- no translation found for setup_step1_instruction (2578631936624637241) -->
+    <skip />
+    <!-- no translation found for setup_step1_finished_instruction (10761482004957994) -->
+    <skip />
+    <!-- no translation found for setup_step1_action (4366513534999901728) -->
+    <skip />
+    <!-- no translation found for setup_step2_title (6860725447906690594) -->
+    <skip />
+    <!-- no translation found for setup_step2_instruction (9141481964870023336) -->
+    <skip />
+    <!-- no translation found for setup_step2_action (1660330307159824337) -->
+    <skip />
+    <!-- no translation found for setup_step3_title (3154757183631490281) -->
+    <skip />
+    <!-- no translation found for setup_step3_instruction (8025981829605426000) -->
+    <skip />
+    <!-- no translation found for setup_step3_action (600879797256942259) -->
+    <skip />
+    <!-- no translation found for setup_finish_action (276559243409465389) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon (5008028590593710830) -->
+    <skip />
+    <!-- no translation found for show_setup_wizard_icon_summary (4119998322536880213) -->
     <skip />
     <!-- no translation found for app_name (6320102637491234792) -->
     <skip />
@@ -373,4 +405,44 @@
     <skip />
     <!-- no translation found for toast_downloading_suggestions (1313027353588566660) -->
     <skip />
+    <!-- no translation found for version_text (2715354215568469385) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_menu_title (1254195365689387076) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_title (4096700390211748168) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_screen_title (5818914331629278758) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_more_options (5671682004887093112) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_less_options (2716586567241724126) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_dialog_confirm (4703129507388332950) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_option_name (6665558053408962865) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_option_name (3094731590655523777) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_locale_option_name (4738643440987277705) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_word_hint (4902434148985906707) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_add_shortcut_hint (2265453012555060178) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_edit_dialog_title (3765774633869590352) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_edit_title (6812255903472456302) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_context_menu_delete_title (8142932447689461181) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_empty_text (558499587532668203) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_all_languages (8276126583216298886) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_more_languages (7131268499685180461) -->
+    <skip />
+    <!-- no translation found for user_dict_settings_delete (110413335187193859) -->
+    <skip />
+    <!-- no translation found for user_dict_fast_scroll_alphabet (5431919401558285473) -->
+    <skip />
 </resources>
diff --git a/java/res/values-ro/dictionary-pack.xml b/java/res/values-ro/strings-appname.xml
similarity index 63%
rename from java/res/values-ro/dictionary-pack.xml
rename to java/res/values-ro/strings-appname.xml
index f65d45b..3b0ca4f 100644
--- a/java/res/values-ro/dictionary-pack.xml
+++ b/java/res/values-ro/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Tastatură Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Verificator ortografic Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Setări tastatură Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Setări verificator ortografic Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 9081f03..69baa8a 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Fără întârziere"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Prestabilit"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> msec."</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Valoare prestabilită"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sugeraţi nume din Agendă"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Utilizaţi numele din Agendă pentru sugestii şi corecţii"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Inserează punct spațiu"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dubla atingere a barei de spațiu inserează punct urmat de spațiu"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalizare"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Scrie cu majusculă primul cuvânt din fiecare propoziţie"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Dicționar personal"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dicţionare suplimentare"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Dicţionar principal"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Afişaţi sugestii de corectare"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Afişaţi întotdeauna"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Afişaţi în modul Portret"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ascundeţi întotdeauna"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blocați cuvintele jignitoare"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Nu se sugerează cuvinte potențial jignitoare"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autocorectare"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Corectare automată cuvinte prin bară spaţiu/semne punctuaţie"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Dezactivată"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Doriți să instalați acest fișier pentru <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"A apărut o eroare"</string>
     <string name="button_default" msgid="3988017840431881491">"Prestabilit"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Limbă și introducere de text"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Alegeți metoda de introducere de text"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Bun venit la <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"cu Tastarea gestuală"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Începeți"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Pasul următor"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Configurarea <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Activați <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Bifați „<xliff:g id="APPLICATION_NAME">%s</xliff:g>” din setările Limbă și introducere de text. Astfel, o autorizați să ruleze pe dispozitiv."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> este activată deja în setările Limbă și introducere de text, deci ați completat acest pas. Treceți acum la următorul!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Activați în Setări"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Comutați la <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Apoi, selectați „<xliff:g id="APPLICATION_NAME">%s</xliff:g>” ca metodă de introducere a textului activă."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Comutați între metodele de introducere a textului"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Felicitări, sunteți gata!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Acum, puteți introduce text în toate aplicațiile preferate, utilizând <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Configurați limbi suplimentare"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Finalizat"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Afișați pictograma aplicației"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Afișați pictograma aplicației în lansator"</string>
     <string name="app_name" msgid="6320102637491234792">"Furnizor dicționar"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Furnizor dicționar"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Serviciu dicționar"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Este disponibil un dicționar pentru <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Apăsați pentru examinare și descărcare"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Se descarcă: sugestiile pentru <xliff:g id="LANGUAGE">%1$s</xliff:g> vor fi gata în curând."</string>
+    <string name="version_text" msgid="2715354215568469385">"Versiunea <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Adăugați"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Adăugați în dicționar"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Expresie"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Alte opțiuni"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Puține opțiuni"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Cuvânt:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Comandă rapidă:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Limbă:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Introduceți un cuvânt"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Comandă rapidă opțională"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Editați cuvântul"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Editați"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Ștergeți"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Nu există cuvinte în dicționarul utilizatorului. Puteți adăuga un cuvânt atingând butonul Adăugați (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Pentru toate limbile"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Mai multe limbi…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Ștergeți"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" AĂÂBCDEFGHIÎJKLMNOPQRSȘTȚUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-ru/dictionary-pack.xml b/java/res/values-ru/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-ru/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-ru/strings-appname.xml b/java/res/values-ru/strings-appname.xml
new file mode 100644
index 0000000..d364d44
--- /dev/null
+++ b/java/res/values-ru/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Клавиатура Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Проверка правописания Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Настройки клавиатуры Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Настройки проверки правописания Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 7bf10ae..2c350d1 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -37,22 +33,24 @@
     <string name="misc_category" msgid="6894192814868233453">"Другие варианты"</string>
     <string name="advanced_settings" msgid="362895144495591463">"Расширенные настройки"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"Для опытных пользователей"</string>
-    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Другой способ ввода"</string>
+    <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Смена способов ввода"</string>
     <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Клавиша переключения языков также служит для смены способа ввода"</string>
     <string name="show_language_switch_key" msgid="5915478828318774384">"Клавиша смены языка"</string>
     <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Показывать, когда включено несколько раскладок"</string>
-    <string name="sliding_key_input_preview" msgid="6604262359510068370">"Показывать индикатор перехода"</string>
-    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Индикатор перехода между регистрами или цифр. и букв. режимами"</string>
+    <string name="sliding_key_input_preview" msgid="6604262359510068370">"След от переключателя режима"</string>
+    <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Показывать след при проведении пальцем от кнопок Shift и \"Символы\""</string>
     <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Задержка закрытия"</string>
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задержки"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"По умолчанию"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> мс"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"По умолчанию"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Подсказывать имена"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Подсказывать исправления на основе имен из списка контактов"</string>
-    <string name="use_double_space_period" msgid="8781529969425082860">"Точка с пробелом"</string>
+    <string name="use_double_space_period" msgid="8781529969425082860">"Ставить точки автоматически"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Вводить точку с пробелом двойным нажатием кнопки \"Пробел\"."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Заглавные автоматически"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Писать первое слово предложения с прописной буквы"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Пользовательский словарь"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Дополнительные словари"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основной словарь"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Варианты исправлений"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Всегда предлагать"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Только в вертикальном режиме"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Никогда не предлагать"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Блокировка нецензурных слов"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Не предлагать слова, которые могут быть сочтены оскорбительными"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Автоисправление"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Автоматическое исправление опечаток при вводе знака препинания или пробела"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Откл."</string>
@@ -165,14 +165,30 @@
     <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="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>
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Установить этот файл для следующего языка: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ошибка"</string>
     <string name="button_default" msgid="3988017840431881491">"По умолчанию"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Язык и ввод"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Выберите способ ввода"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Представляем приложение \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\""</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"с непрерывным вводом"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Начать работу"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Далее"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>: настройка"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>: включение"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Установите флажок <xliff:g id="APPLICATION_NAME">%s</xliff:g> в меню \"Язык и ввод\", чтобы использовать этот способ ввода на устройстве."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Приложение <xliff:g id="APPLICATION_NAME">%s</xliff:g> уже включено в настройках языка и ввода, поэтому можно перейти к следующему шагу."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Включить в настройках"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>: активация"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Выберите приложение \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" как текущий способ ввода."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Другой способ ввода"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Настройте дополнительные языки."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Теперь вы можете использовать приложение \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" для набора текста."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Настроить дополнительные языки"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Готово"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Показывать значок приложения"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Отображать значок приложения на панели запуска"</string>
     <string name="app_name" msgid="6320102637491234792">"Поставщик словарей"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Поставщик словарей"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Служба словарей"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Доступен словарь: <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Нажмите, чтобы просмотреть и загрузить"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Загрузка словаря: <xliff:g id="LANGUAGE">%1$s</xliff:g>…"</string>
+    <string name="version_text" msgid="2715354215568469385">"Версия <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Добавить"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Добавление в словарь"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Фраза"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Больше настроек"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Меньше настроек"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ОК"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Слово:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Быстрые клавиши:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Язык:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Введите слово"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Ярлык для фразы (необязательно)"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Изменение слова"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Изменить"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Удалить"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"В словаре пользователя пока ничего нет. Добавлять слова можно с помощью кнопки \"Добавить ( + )\"."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Для всех языков"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Другой язык"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Удалить"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"</string>
 </resources>
diff --git a/java/res/values-sk/dictionary-pack.xml b/java/res/values-sk/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-sk/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-sk/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-sk/strings-appname.xml
index f65d45b..1a7a43a 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-sk/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Klávesnica Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Kontrola pravopisu (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Nastavenia klávesnice Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Nastavenia kontroly pravopisu Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index b01e325..4b652d0 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Bez oneskorenia"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Predvolená"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Predvolené nastav."</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Navrhnúť mená kontaktov"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Používať mená z Kontaktov na návrhy a opravy"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Bodka s medzerou"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dvojitým klepnutím na medzerník vložíte bodku a medzeru."</string>
     <string name="auto_cap" msgid="1719746674854628252">"Veľké písmená automaticky"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Písanie prvého slova v každej vete veľkým písmenom"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Osobný slovník"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Doplnkové slovníky"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Hlavný slovník"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Zobraziť návrhy opráv"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vždy zobrazovať"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Zobraziť v režime na výšku"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vždy skrývať"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokovať urážlivé slová"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Nenavrhovať potenciálne urážlivé slová"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Automatické opravy"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Stlačením medzerníka a interpunkcie sa aut. opravia chybné slová"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Vypnuté"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Chcete nainštalovať tento súbor pre jazyk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Vyskytla sa chyba"</string>
     <string name="button_default" msgid="3988017840431881491">"Predvolené"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Jazyk &amp; vstup"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Zvoliť metódu vstupu"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Vitajte v aplikácii <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"s funkciou Písanie gestami"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Začať"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Ďalší krok"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Nastavenie aplikácie <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Povoľte aplikáciu <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"V nastaveniach vstupu a jazyka začiarknite políčko <xliff:g id="APPLICATION_NAME">%s</xliff:g>. Týmto aplikácii povolíte spustenie v zariadení."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Aplikácia <xliff:g id="APPLICATION_NAME">%s</xliff:g> je už povolená v Nastaveniach jazyka a vstupu. Prejdite na ďalší krok."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Povoliť v Nastaveniach"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Prepnite na aplikáciu <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Potom vyberte aplikáciu <xliff:g id="APPLICATION_NAME">%s</xliff:g> ako aktívnu metódu textového vstupu."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Prepnúť metódu vstupu"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Blahoželáme, všetko je nastavené!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Pomocou aplikácie <xliff:g id="APPLICATION_NAME">%s</xliff:g> teraz môžete zadávať text vo všetkých obľúbených aplikáciách."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Nakonfigurujte ďalšie jazyky"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Hotovo"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Zobraziť ikonu aplikácie"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Zobrazenie ikony aplikácie v spúšťači"</string>
     <string name="app_name" msgid="6320102637491234792">"Poskytovateľ slovníka"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Poskytovateľ slovníka"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Služba slovníka"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"K dispozícii je slovník pre jazyk <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Stlačením skontrolujete a prevezmete"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Preberanie: návrhy pre jazyk <xliff:g id="LANGUAGE">%1$s</xliff:g> budú čoskoro k dispozícii."</string>
+    <string name="version_text" msgid="2715354215568469385">"Verzia <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Pridať"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Pridať do slovníka"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fráza"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Ďalšie možnosti"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Menej možností"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Slovo:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Klávesová skratka"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Jazyk:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Zadajte slovo"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Voliteľná skratka"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Upraviť slovo"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Upraviť"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Odstrániť"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"V používateľskom slovníku nie sú žiadne slová. Slovo pridáte dotknutím sa tlačidla Pridať (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Pre všetky jazyky"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Ďalšie jazyky…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Odstrániť"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-sl/dictionary-pack.xml b/java/res/values-sl/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-sl/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-el/dictionary-pack.xml b/java/res/values-sl/strings-appname.xml
similarity index 63%
rename from java/res/values-el/dictionary-pack.xml
rename to java/res/values-sl/strings-appname.xml
index f65d45b..5a9c017 100644
--- a/java/res/values-el/dictionary-pack.xml
+++ b/java/res/values-sl/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Tipkovnica za Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Črkovalnik za Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Nastavitve tipkovnice za Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Nastavitve črkovalnika za Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 9243b12..f2bfc31 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Brez zakasnitve"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Privzeto"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Privzeto v sistemu"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Predlagaj imena stikov"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Uporaba imen iz stikov za predloge in popravke"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dva presl. za vnos pike"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Z dvojnim dotikom preslednice vstavite piko in za njo presledek"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Samod. velike začetnice"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Prvo besedo stavka piši z veliko začetnico"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Osebni slovar"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Dodatni slovarji"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Glavni slovar"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Pokaži predloge popravkov"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Vedno pokaži"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Prikaži v pokončnem načinu"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Vedno skrij"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blokiraj žaljive besede"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ne predlagaj potencialno žaljivih besed"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Samodejni popravek"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Preslednica in ločila samodejno popravijo napačno vtipkane besede"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Izklopljeno"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Zares želite namestiti to datoteko za jezik <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Prišlo je do napake"</string>
     <string name="button_default" msgid="3988017840431881491">"Privzeto"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Jezik in vnos"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Izbira načina vnosa"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Pozdravljeni v aplikaciji <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"s pisanjem s kretnjami"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Začnite"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Naslednji korak"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Nastavitev aplikacije <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Omogočanje aplikacije <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"V nastavitvah za jezik in vnos izberite aplikacijo »<xliff:g id="APPLICATION_NAME">%s</xliff:g>«. S tem ji omogočite izvajanje v napravi."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Aplikacija <xliff:g id="APPLICATION_NAME">%s</xliff:g> je že omogočena v nastavitvah jezika in vnosa, zato je to že opravljeno. Nadaljujte."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Omogoči v nastavitvah"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Preklop na aplikacijo <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Nato izberite aplikacijo »<xliff:g id="APPLICATION_NAME">%s</xliff:g>« kot aktivni način vnosa besedila."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Preklopi način vnosa"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Čestitamo, pripravljeni ste."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Zdaj lahko z aplikacijo <xliff:g id="APPLICATION_NAME">%s</xliff:g> tipkate v vseh svojih priljubljenih aplikacijah."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfiguracija dodatnih jezikov"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Končano"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Pokaži ikono aplikacije"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Prikaz ikone aplikacije v zaganjalniku"</string>
     <string name="app_name" msgid="6320102637491234792">"Ponudnik slovarja"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Ponudnik slovarja"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Storitev slovarja"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Slovar je na voljo za jezik <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pritisnite za pregled in prenos"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Predlogi za prenos za jezik <xliff:g id="LANGUAGE">%1$s</xliff:g> bodo kmalu pripravljeni."</string>
+    <string name="version_text" msgid="2715354215568469385">"Različica <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Dodaj"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Dodaj v slovar"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Besedna zveza"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Več možnosti"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Manj možnosti"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"V redu"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Beseda:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Bližnjica:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Jezik:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Vnesite besedo"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Izbirna bližnjica"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Uredi besedo"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Uredi"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Izbriši"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"V uporabniškem slovarju ni besed. Besede lahko dodate z dotikom gumba »Dodaj« (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Za vse jezike"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Več jezikov ..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Izbriši"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-sr/dictionary-pack.xml b/java/res/values-sr/strings-appname.xml
similarity index 60%
rename from java/res/values-sr/dictionary-pack.xml
rename to java/res/values-sr/strings-appname.xml
index f65d45b..a8c610b 100644
--- a/java/res/values-sr/dictionary-pack.xml
+++ b/java/res/values-sr/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android тастатура (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android провера правописа (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Подешавања Android тастатуре (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Подешавања Android провере правописа (AOSP)"</string>
 </resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index cede67c..af41693 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без одлагања"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Подразумевано"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Подразумевано"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Предложи имена контаката"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Користи имена из Контаката за предлоге и исправке"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Тачка и размак"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Двоструким додиром размака умеће се тачка праћена размаком"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Аутоматски унос великих слова"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Писање великог слова на почетку сваке реченице"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Лични речник"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Помоћни речници"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Главни речник"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Прикажи предлоге за исправку"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Увек прикажи"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Прикажи у усправном режиму"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Увек сакриј"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Блокирај увредљиве речи"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Нема предлагања потенцијално увредљивих речи"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Аутом. исправљање"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Размак и интерпункција аутоматски исправљају грешке у куцању"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Искључи"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Желите ли стварно да инсталирате ову датотеку за <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Дошло је до грешке"</string>
     <string name="button_default" msgid="3988017840431881491">"Подразумевано"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Језик и унос"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Избор метода уноса"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Добро дошли у <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"помоћу Куцања покретима"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Започнимо"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Следећи корак"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Подешавање апликације <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Омогућите апликацију <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Потврдите апликацију „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ у Подешавањима језика и уноса. Тако јој одобравате покретање на уређају."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Апликација <xliff:g id="APPLICATION_NAME">%s</xliff:g> је већ омогућена у Подешавањима језика и уноса тако да је овај корак готов. Пређимо на следећи!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Омогући у Подешавањима"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Пребаците на <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Затим изаберите „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ као активни метод уноса текста."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Пребаци методе уноса"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Честитамо, све је спремно!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Сада можете да куцате у свим омиљеним апликацијама помоћу <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Конфигуриши додатне језике"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Завршено"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Прикажи икону апликације"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Икона апликације се приказује у покретачу"</string>
     <string name="app_name" msgid="6320102637491234792">"Добављач речника"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Добављач речника"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Услуга речника"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Речник је доступан за <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Притисните за преглед и преузимање"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Преузимање: Предлози за <xliff:g id="LANGUAGE">%1$s</xliff:g> ће ускоро бити спремни."</string>
+    <string name="version_text" msgid="2715354215568469385">"Верзија <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Додај"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Додавање у речник"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Фраза"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Више опција"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Мање опција"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Потврди"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Реч:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Пречица:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Језик:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Унесите реч"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Опционална пречица"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Измена речи"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Измени"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Избриши"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Немате ниједну реч у корисничком речнику. Додајте реч додиром на дугме Додај (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"За све језике"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Још језика..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Избриши"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" АБВГДЂЕЖЗИЈКЛЉМНЊОПРСТЋУФХЦЧЏШ"</string>
 </resources>
diff --git a/java/res/values-sv/dictionary-pack.xml b/java/res/values-sv/strings-appname.xml
similarity index 62%
rename from java/res/values-sv/dictionary-pack.xml
rename to java/res/values-sv/strings-appname.xml
index f65d45b..5d750cc 100644
--- a/java/res/values-sv/dictionary-pack.xml
+++ b/java/res/values-sv/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Androids tangentbord (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Stavningskontroll i Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Inställningar för Androids tangentbord (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Inställningar för Androids stavningskontroll (AOSP)"</string>
 </resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 8ac6c2c..539623b 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Fördröj inte"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Standard"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> millisek."</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Standardinställning"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Föreslå kontaktnamn"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Använd namn från Kontakter för förslag och korrigeringar"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dubbelt blanksteg = punkt"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Dubbelt blanksteg ger en punkt följt av mellanslag"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Automatiska versaler"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Automatisk stor bokstav först i varje mening"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Egen ordlista"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Tilläggsordlistor"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Huvudordlistan"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Visa rättningsförslag"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Visa alltid"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Visa i stående format"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Dölj alltid"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Blockera stötande ord"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Visa inte förslag på ord som kan verka stötande"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Autokorrigering"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Blanksteg/skiljetecken rättar felstavning"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Av"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vill du verkligen installera filen för <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ett fel uppstod"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Språk &amp; inmatning"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Välj inmatningsmetod"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Välkommen till <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"med svepskrivning"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Kom igång"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Nästa steg"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Konfigurera <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Aktivera <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Markera <xliff:g id="APPLICATION_NAME">%s</xliff:g> i inställningarna för språk och inmatning så att appen kan köras."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> har redan aktiverats i inställningarna för Språk och inmatning och det här steget är färdigt. Fortsätt till nästa steg."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Aktivera i inställningarna"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Byt till <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Välj sedan <xliff:g id="APPLICATION_NAME">%s</xliff:g> som din aktiva textinmatningsmetod."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Byt inmatningsmetod"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Grattis! Nu är det klart."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Nu kan du skriva i alla dina favoritappar med <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Konfigurera ytterligare språk"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Slutförda"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Visa appikon"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Visa appikonen i startprogrammet"</string>
     <string name="app_name" msgid="6320102637491234792">"Dictionary Provider"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Dictionary Provider"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Ordlistetjänst"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"En ordlista är tillgänglig för <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Tryck om du vill granska och hämta"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Hämtar: förslag för <xliff:g id="LANGUAGE">%1$s</xliff:g> är snart klara."</string>
+    <string name="version_text" msgid="2715354215568469385">"Version <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Lägg till"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Lägg till i ordlista"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fras"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Fler alternativ"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Färre alternativ"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Ord:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Genväg:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Språk:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Skriv ett ord"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Valfri genväg"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Redigera ord"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Redigera"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Ta bort"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Det finns inga ord i användarordlistan. Du kan lägga till ord med knappen Lägg till (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"För alla språk"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Fler språk ..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Ta bort"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-sw/dictionary-pack.xml b/java/res/values-sw/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-sw/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-sw/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-sw/strings-appname.xml
index f65d45b..354afdd 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-sw/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Kibodi ya Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Kikagua-tahajia cha Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Mipangilio ya Kibodi ya Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Mipangilio ya Kikagua-tahajia cha Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 3ae4009..cd31740 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -35,7 +31,7 @@
     <string name="correction_category" msgid="2236750915056607613">"Marekebisho ya maandishi"</string>
     <string name="gesture_typing_category" msgid="497263612130532630">"Kuandika kwa ishara"</string>
     <string name="misc_category" msgid="6894192814868233453">"Chaguo zingine"</string>
-    <string name="advanced_settings" msgid="362895144495591463">"Mipangilio mahiri"</string>
+    <string name="advanced_settings" msgid="362895144495591463">"Mipangilio ya kina"</string>
     <string name="advanced_settings_summary" msgid="4487980456152830271">"Chaguo za wataalamu"</string>
     <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Badilisha hadi kwa mbinu zingine za ingizo"</string>
     <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Ufunguo wa kubadilisha lugha unashughulikia mbinu zingine za ingizo pia"</string>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Hakuna kuchelewa"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Chaguo-msingi"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"Milisekunde <xliff:g id="MILLISECONDS">%s</xliff:g>"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Chaguo-msingi la mfumo"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Pendekeza majini ya Anwani"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Tumia majina kutoka kwa Anwani kwa mapendekezo na marekebisho"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Kitone baada ya nafasi mbili"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Kugonga mara mbili kwenye upau nafasi kunaingiza kitone kikifuatiwa na nafasi"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Uwekaji wa herufi kubwa kiotomatiki"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Fanya herufi kubwa neno la kwanza la kila sentensi"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Kamusi ya kibinafsi"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Nyongeza za kamusi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Kamusi kuu"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Onyesha mapendekezo ya marekebisho"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Onyesha kila wakati"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Onyesha katika hali wima"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Ficha kila wakati"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Zuia maneno yanayokera"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Usipendekeze maneno yanayoweza kukera"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Usahihishaji otomatiki"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Kiaamba na kiakifishi hurekebisha maneno ambayo yamechapishwa vibaya"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Zima"</string>
@@ -75,7 +75,7 @@
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Onyesha neno lililopendekezwa unapoonyesha ishara"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Imehifadhiwa"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Nenda"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Ifuatayo"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Inayofuata"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"Iliyotangulia"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Kwisha"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Tuma"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Isakinishe faili hii kwa <xliff:g id="LOCALE_NAME">%s</xliff:g> kweli?"</string>
     <string name="error" msgid="8940763624668513648">"Kulikuwa na hitilafu"</string>
     <string name="button_default" msgid="3988017840431881491">"Chaguo-msingi"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Lugha na uingizaji"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Chagua mbinu ya kuingiza data"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Karibu kwenye <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"kwa Kuandika kwa ishara"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Anza kutumia"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Hatua inayofuata"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Inasanidi <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Washa <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Tafadhali angalia \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" katika lugha yako na mipangilio ya kuingiza. Hii itaidhinisha ili iendeshwe kwenye kifaa chako."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> tayari imewashwa katika mipangilio yako ya Lugha, Kibodi na Sauti, kwa hivyo hatua hii imekamilika. Nenda kwenye hatua inayofuata!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Washa katika Mipangilio"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Badilisha kwenda <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Kisha, chagua \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" kama mbinu yako inayotumika ya kuingiza data ya maandishi."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Badilisha mbinu za kuingiza data"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Hongera, uko tayari!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Sasa unaweza kuchapa programu zako zote uzipendazo ukitumia <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Sanidi lugha za ziada"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Imemaliza"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Onyesha ikoni ya programu"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Onyesha ikoni ya programu kwenye kizinduzi"</string>
     <string name="app_name" msgid="6320102637491234792">"Programu ya kamusi"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Programu ya kamusi"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Huduma ya Kamusi"</string>
@@ -203,4 +219,24 @@
     <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>
+    <string name="version_text" msgid="2715354215568469385">"Toleo la <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Ongeza"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Ongeza kwenye kamusi"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Fungu la maneno"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Hiari zingine"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Hiari chache"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Sawa"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Neno:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Njia ya mkato:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Lugha:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Chapa neno"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Njia ya mkato ya hiari"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Badilisha neno"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Hariri"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Futa"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Huna maneno yoyote katika kamusi ya mtumiaji. Ongeza neno kwa kugusa kitufe cha Ongeza (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Ya lugha zote"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Lugha zingine..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Futa"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-th/dictionary-pack.xml b/java/res/values-th/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-th/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-th/strings-appname.xml b/java/res/values-th/strings-appname.xml
new file mode 100644
index 0000000..af6d1a9
--- /dev/null
+++ b/java/res/values-th/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"แป้นพิมพ์แอนดรอยด์ (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"เครื่องตรวจตัวสะกดแอนดรอยด์ (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"การตั้งค่าแป้นพิมพ์แอนดรอยด์ (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"การตั้งค่าเครื่องตรวจตัวสะกดแอนดรอยด์ (AOSP)"</string>
+</resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index b6a0d66..908a799 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"ไม่มีการหน่วงเวลา"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ค่าเริ่มต้น"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> มิลลิวิ"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"ค่าเริ่มต้นของระบบ"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"แนะนำชื่อผู้ติดต่อ"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"ใช้ชื่อจากรายชื่อติดต่อสำหรับคำแนะนำและการแก้ไข"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"แตะ Space สองครั้งแทรกจุด"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"แตะ Spacebar สองครั้งจะแทรกจุดตามด้วยช่องว่างหนึ่งช่อง"</string>
     <string name="auto_cap" msgid="1719746674854628252">"ปรับเป็นตัวพิมพ์ใหญ่อัตโนมัติ"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"ทำให้คำแรกของทุกประโยคเป็นตัวพิมพ์ใหญ่"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"พจนานุกรมส่วนตัว"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"พจนานุกรม Add-On"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"พจนานุกรมหลัก"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"แสดงคำแนะนำการแก้ไข"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"แสดงทุกครั้ง"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"แสดงในโหมดแนวตั้ง"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ซ่อนทุกครั้ง"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"บล็อกคำที่ไม่เหมาะสม"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"ไม่แนะนำคำที่อาจไม่เหมาะสม"</string>
     <string name="auto_correction" msgid="7630720885194996950">"การแก้ไขอัตโนมัติ"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"กดเว้นวรรคและเครื่องหมายจะแก้คำผิดอัตโนมัติ"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"ปิด"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ติดตั้งไฟล์นี้สำหรับ <xliff:g id="LOCALE_NAME">%s</xliff:g> จริงๆ หรือ"</string>
     <string name="error" msgid="8940763624668513648">"เกิดข้อผิดพลาด"</string>
     <string name="button_default" msgid="3988017840431881491">"ค่าเริ่มต้น"</string>
-    <string name="language_settings" msgid="1671153053201809031">"ภาษาและการป้อนข้อมูล"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"เลือกวิธีการป้อนข้อมูล"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"ยินดีต้อนรับสู่ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"พร้อมการป้อนข้อมูลด้วยท่าทาง"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"เริ่มต้นใช้งาน"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"ขั้นตอนถัดไป"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"การตั้งค่า <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"เปิดใช้งาน <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"โปรดตรวจสอบ \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ในการตั้งค่าภาษาและการป้อนข้อมูลของคุณ ซึ่งจะอนุญาตให้แอปทำงานบนอุปกรณ์ของคุณ"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> เปิดใช้งานในภาษาและการตั้งค่าการป้อนข้อมูลของคุณอยู่แล้ว ดังนั้น ขั้นตอนนี้จึงเสร็จสิ้นแล้ว ไปยังขั้นตอนต่อไป!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"เปิดใช้งานในการตั้งค่า"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"สลับไปใช้ <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"ถัดไป เลือก \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" เป็นวิธีการป้อนข้อความที่ใช้งานของคุณ"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"สลับวิธีการป้อนข้อมูล"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"ยินดีด้วย คุณพร้อมใช้งานแล้ว!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"ตอนนี้คุณสามารถพิมพ์ข้อมูลลงในแอปที่ชื่นชอบทั้งหมดด้วย <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"กำหนดค่าภาษาเพิ่มเติม"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"เสร็จสิ้น"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"แสดงไอคอนแอป"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"แสดงไอคอนแอปพลิเคชันในตัวเรียกใช้งาน"</string>
     <string name="app_name" msgid="6320102637491234792">"ผู้ให้บริการพจนานุกรม"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"ผู้ให้บริการพจนานุกรม"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"บริการพจนานุกรม"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"มีพจนานุกรมให้ใช้งานในภาษา <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"กดเพื่อตรวจสอบและดาวน์โหลด"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"กำลังดาวน์โหลด: คำแนะนำสำหรับ <xliff:g id="LANGUAGE">%1$s</xliff:g> จะพร้อมใช้งานเร็วๆ นี้"</string>
+    <string name="version_text" msgid="2715354215568469385">"เวอร์ชัน <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"เพิ่ม"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"เพิ่มในพจนานุกรม"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ข้อความ"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"ตัวเลือกอื่น"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ลดตัวเลือก"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ตกลง"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"คำ:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"ทางลัด:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ภาษา:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"พิมพ์คำ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"ทางลัดที่ไม่บังคับ"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"แก้ไขคำ"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"แก้ไข"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"ลบ"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"คุณไม่มีคำในพจนานุกรมผู้ใช้เลย เพิ่มคำโดยแตะปุ่มเพิ่ม (+)"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"สำหรับทุกภาษา"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"ภาษาเพิ่มเติม…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"ลบ"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-tl/dictionary-pack.xml b/java/res/values-tl/strings-appname.xml
similarity index 63%
rename from java/res/values-tl/dictionary-pack.xml
rename to java/res/values-tl/strings-appname.xml
index f65d45b..200ee5f 100644
--- a/java/res/values-tl/dictionary-pack.xml
+++ b/java/res/values-tl/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android Keyboard (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Spell Checker ng Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Mga Setting ng Android Keyboard (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Mga Setting ng Spell Checker ng Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 6f8add7..e72a798 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Walang antala"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Default"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Default ng system"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Mungkahi pangalan Contact"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Gamitin pangalan mula Mga Contact sa mga mungkahi\'t pagwawasto"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Double-space period"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Naglalagay ng tuldok na may puwang ang pag-double tap sa spacebar"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Auto-capitalization"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"I-capitalize ang unang salita ng bawat pangungusap"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Personal na diksyunaryo"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Mga diksyunaryo na add-on"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Pangunahing diksyunaryo"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Magpakita ng mga suhestiyon ng pagwawasto"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Palaging ipakita"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Ipakita sa portrait na mode"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Palaging itago"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"I-block nakakapanakit na salita"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Huwag magmungkahi ng mga maaaring nakakapanakit na salita"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Awtomatiko pagwasto"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Awto tinatama ng spacebar at bantas ang maling na-type"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Naka-off"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"I-install talaga ang file na ito para sa <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Nagkaroon ng error"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Wika at input"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Pumili ng pamamaraan ng pag-input"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Maligayang pagdating sa <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"gamit ang Gesture na Pag-type"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Magsimula"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Susunod na hakbang"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Sine-set up ang <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Paganahin ang <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Paki-check ang \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" sa mga setting mo ng Wika at input. Mapapahintulutan itong tumakbo sa device mo."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Naka-enable na ang <xliff:g id="APPLICATION_NAME">%s</xliff:g> sa iyong Wika at mga setting ng pag-input, kaya tapos na ang hakbang na ito. Magpatuloy sa susunod!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"I-enable sa Mga Setting"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Lumipat sa <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Susunod, piliin ang \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" bilang iyong aktibong pamamaraan ng pag-input ng teksto."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Magpalit ng pamamaraan ng pag-input"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Binabati kita, handa ka na!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Ngayon, mata-type mo na ang lahat ng paborito mong apps gamit ang <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Mag-configure ng mga karagdagang wika"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Tapos na"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Ipakita ang icon ng app"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Ipakita ang icon ng application sa launcher"</string>
     <string name="app_name" msgid="6320102637491234792">"Provider ng Diksyunaryo"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Provider ng Diksyunaryo"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Serbisyo ng Diksyunaryo"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"May available na diksyunaryo para sa <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Pindutin upang suriin at i-download"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Dina-download: malapit nang maging handa ang mga suhestiyon para sa <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Bersyon <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Idagdag"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Idagdag sa diksyunaryo"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Parirala"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Higit pa"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Mas kaunti"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Salita:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Shortcut:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Wika:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Mag-type ng salita"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Opsyonal na shortcut"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"I-edit ang salita"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"I-edit"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Tanggalin"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Wala kang anumang mga salita sa diksyunaryo ng user. Magdagdag ng salita sa pamamagitan ng pagpindot sa button na Magdagdag (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Para sa lahat ng wika"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Higit pang mga wika..."</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Tanggalin"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-tr/dictionary-pack.xml b/java/res/values-tr/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-tr/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-tr/strings-appname.xml
similarity index 63%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-tr/strings-appname.xml
index f65d45b..4eb8ab7 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-tr/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android Klavye (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android Yazım Denetleyici (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android Klavye Ayarları (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android Yazım Denetleyici Ayarları (AOSP)"</string>
 </resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index a0a6a67..26dfe70 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Gecikme yok"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Varsayılan"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Sistem varsayılanı"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Kişi Adları öner"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Öneri ve düzeltmeler için Kişiler\'deki adları kullan"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Çift boşlukla nokta ekleme"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Boşluk çubuğuna iki kez vurmak nokta ve ardından bir boşluk ekler"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Otomatik olarak büyük harf yap"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Her cümlenin ilk kelimesini büyük harf yap"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Kişisel sözlük"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Ek sözlükler"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Ana sözlük"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Düzeltme önerilerini göster"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Her zaman göster"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Dikey modda göster"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Her zaman gizle"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Rahatsız edici kelimeleri engelle"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Rahatsız edici olabilecek kelimeleri önerme"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Otomatik düzeltme"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Boşluk tuşu ve noktalama işaretleri yanlış yazılan kelimeleri otomatikman düzeltir"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Kapalı"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> için bu dosya gerçekten yüklensin mi?"</string>
     <string name="error" msgid="8940763624668513648">"Bir hata oluştu"</string>
     <string name="button_default" msgid="3988017840431881491">"Varsayılan"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Dil ve giriş"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Giriş yöntemini seçin"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> uygulamasına hoş geldiniz"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"Hareketle Yazmayı içerir"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Başlayın"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Sonraki adım"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> uygulamasını kurma"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> uygulamasını etkinleştirin"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Lütfen \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" uygulamasını kendi Dil ve giriş ayarlarınızda işaretleyin. Bu işlem, uygulamaya cihazınızda çalışma yetkisi verecektir."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> zaten Diliniz ve giriş ayarlarınızda etkinleştirilmiş durumda, dolayısıyla bu adım tamamlanmıştır. Bir sonrakine geçin!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Ayarlarda etkinleştir"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> uygulamasına geçin"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Sonra, \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" uygulamasını etkin metin giriş yönteminiz olarak seçin."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Giriş yöntemini değiştir"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Tebrikler, bitirdiniz!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Artık <xliff:g id="APPLICATION_NAME">%s</xliff:g> ile tüm favori uygulamalarınızda yazabilirsiniz."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Ek dilleri yapılandırın"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Tamamlandı"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Uygulama simgesini göster"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Uygulama simgesini başlatıcıda göster"</string>
     <string name="app_name" msgid="6320102637491234792">"Sözlük Sağlayıcı"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Sözlük Sağlayıcı"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Sözlük Hizmeti"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g> için kullanılabilecek bir sözlük mevcut"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"İncelemek ve indirmek için basın"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"<xliff:g id="LANGUAGE">%1$s</xliff:g> için önerilerin indirilmesine kısa süre içinde başlanacak."</string>
+    <string name="version_text" msgid="2715354215568469385">"Sürüm <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Ekle"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Sözlüğe ekle"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Kelime öbeği"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Daha çok seçenek"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Daha az seçenek"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"Tamam"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Kelime:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Kısayol:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Dil:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Bir kelime yazın"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"İsteğe bağlı kısayol"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Kelimeyi düzenle"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Düzenle"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Sil"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Kullanıcı sözlüğünde hiç kelimeniz yok. Ekle (+) düğmesini kullanarak kelime ekleyin."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Tüm diller için"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Diğer diller…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Sil"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCÇDEFGĞHIİJKLMNOÖPQRSŞTUÜVWXYZ"</string>
 </resources>
diff --git a/java/res/values-uk/dictionary-pack.xml b/java/res/values-uk/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-uk/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-uk/strings-appname.xml b/java/res/values-uk/strings-appname.xml
new file mode 100644
index 0000000..44dd5b7
--- /dev/null
+++ b/java/res/values-uk/strings-appname.xml
@@ -0,0 +1,27 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Клавіатура Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Перевірка орфографії Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Налаштування клавіатури Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Налаштування перевірки орфографії Android (AOSP)"</string>
+</resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 876ec0f..a24eab7 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без затримки"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"За умовчанням"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> мс"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"За умовчанням"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Пропон. імена контактів"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Використ. імена зі списку контактів для пропозицій і виправлень"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Крапка подвійним пробілом"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Подвійне натискання пробілу вставляє крапку з пробілом після неї"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Авто викор. вел. літер"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Писати перше слово в кожному реченні з великої літери"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Особистий словник"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Додані словники"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Основний словник"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Показувати пропозиції виправлень"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Завжди показувати"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Показувати в книжковій орієнтації"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Завжди ховати"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Блокувати образливі слова"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Не пропонувати потенційно образливі слова"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Автовиправлення"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Пробіл і пунктуація автоматично виправляють слова з помилками"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Вимк."</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Справді встановити цей файл для такої мови: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Сталася помилка"</string>
     <string name="button_default" msgid="3988017840431881491">"За умовчанням"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Мова та введення"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Вибрати метод введення"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Вітаємо в програмі <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"з функцією Ввід жестами"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Розпочати"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Наступний крок"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Налаштування програми <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Увімкніть програму <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Виберіть \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" у налаштуваннях \"Мова та введення\", щоб дозволити запуск цієї програми на пристрої."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"Програму <xliff:g id="APPLICATION_NAME">%s</xliff:g> уже ввімкнено в налаштуваннях мови та введення. Перейдіть до наступного кроку."</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Увімкнути в налаштуваннях"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Перейдіть до програми <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Далі виберіть \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" як поточний метод введення тексту."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Змінити метод введення"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Вітаємо! Налаштування завершено."</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Тепер ви можете вводити текст у всіх своїх улюблених програмах за допомогою <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Налаштувати додаткові мови"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Завершено"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Показувати піктограму програми"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Відображати піктограму програми на панелі запуску"</string>
     <string name="app_name" msgid="6320102637491234792">"Постачальник словника"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Постачальник словника"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Служба словника"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Доступний словник для такої мови: <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Натисніть, щоб переглянути та завантажити"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Скоро почнеться завантаження пропозицій для такої мови: <xliff:g id="LANGUAGE">%1$s</xliff:g>."</string>
+    <string name="version_text" msgid="2715354215568469385">"Версія <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Додати"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Додати в словник"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Фраза"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Інші варіанти"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Менше опцій"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"ОК"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Слово:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Ярлик:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Мова:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Введіть слово"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Необов’язковий ярлик"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Редагувати слово"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Редагувати"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Видалити"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"У словнику користувача немає жодного слова. Додайте слово, торкнувшись кнопки \"Додати\" (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Для всіх мов"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Інші мови…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Видалити"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" АБВГҐДЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ"</string>
 </resources>
diff --git a/java/res/values-vi/dictionary-pack.xml b/java/res/values-vi/strings-appname.xml
similarity index 62%
rename from java/res/values-vi/dictionary-pack.xml
rename to java/res/values-vi/strings-appname.xml
index f65d45b..e4b0f65 100644
--- a/java/res/values-vi/dictionary-pack.xml
+++ b/java/res/values-vi/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Bàn phím Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Trình kiểm tra chính tả Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Cài đặt bàn phím Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Cài đặt trình kiểm tra chính tả Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index d062630..6d9bde5 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Không có tgian trễ"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Mặc định"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> mili giây"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Mặc định của hệ thống"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Đề xuất tên liên hệ"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Sử dụng tên từ Danh bạ cho các đề xuất và chỉnh sửa"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Dấu cách đôi"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Nhấn đúp vào phím cách sẽ chèn thêm một dấu sau dấu cách"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Tự động viết hoa"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Viết hoa chữ đầu tiên của mỗi câu"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Từ điển cá nhân"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Thêm từ điển"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Từ điển chính"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Hiển thị gợi ý sửa"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Luôn hiển thị"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Hiển thị ở chế độ dọc"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Luôn ẩn"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Chặn các từ xúc phạm"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Không đề xuất các từ có thể gây xúc phạm"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Tự động sửa"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Phím cách và dấu câu tự động sửa từ nhập sai"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Tắt"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Thực sự cài đặt tệp này cho <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Đã xảy ra lỗi"</string>
     <string name="button_default" msgid="3988017840431881491">"Mặc định"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Ngôn ngữ và phương thức nhập"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Chọn phương thức nhập"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Chào mừng bạn đến với <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"với Nhập bằng cử chỉ"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Bắt đầu"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Bước tiếp theo"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Thiết lập <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Bật <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Vui lòng kiểm tra \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" trong cài đặt ngôn ngữ và phương thức nhập của bạn. Điều này sẽ ủy quyền cho ứng dụng chạy trên thiết bị của bạn."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> đã được bật trong cài đặt Ngôn ngữ và phương thức nhập, do đó bước này đã hoàn tất. Hãy chuyển sang bước tiếp theo!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Bật trong Cài đặt"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Chuyển sang <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Tiếp theo, chọn \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" làm phương thức nhập văn bản hoạt động của bạn."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Chuyển phương thức nhập"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Xin chúc mừng, bạn đã cài đặt xong!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Bây giờ bạn có thể nhập vào tất cả ứng dụng yêu thích của mình với <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Định cấu hình các ngôn ngữ khác"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Đã xong"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Hiển thị biểu tượng ứng dụng"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Hiển thị biểu tượng ứng dụng trong trình khởi chạy"</string>
     <string name="app_name" msgid="6320102637491234792">"Nhà cung cấp từ điển"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Nhà cung cấp từ điển"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Dịch vụ từ điển"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Có sẵn từ điển cho <xliff:g id="LANGUAGE">%1$s</xliff:g>"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Nhấn để xem lại và tải xuống"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Tải xuống: đề xuất đối với <xliff:g id="LANGUAGE">%1$s</xliff:g> sẽ sớm sẵn sàng."</string>
+    <string name="version_text" msgid="2715354215568469385">"Phiên bản <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Thêm"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Thêm vào từ điển"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Cụm từ"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Thêm tùy chọn"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Bớt tùy chọn"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"OK"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Từ:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Phím tắt:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Ngôn ngữ:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Nhập một từ"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Phím tắt tùy chọn"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Chỉnh sửa từ"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Chỉnh sửa"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Xóa"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Bạn không có bất kỳ từ nào trong từ điển người dùng. Bạn có thể thêm từ bằng cách chạm vào nút Thêm (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Cho tất cả ngôn ngữ"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Ngôn ngữ khác…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Xóa"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-zh-rCN/dictionary-pack.xml b/java/res/values-zh-rCN/strings-appname.xml
similarity index 64%
rename from java/res/values-zh-rCN/dictionary-pack.xml
rename to java/res/values-zh-rCN/strings-appname.xml
index f65d45b..3f74ca4 100644
--- a/java/res/values-zh-rCN/dictionary-pack.xml
+++ b/java/res/values-zh-rCN/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android 键盘 (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android 拼写检查工具 (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android 键盘设置 (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android 拼写检查工具设置 (AOSP)"</string>
 </resources>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 75b589d..14198e5 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"无延迟"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"默认"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>毫秒"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"系统默认值"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"联系人姓名建议"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"根据通讯录中的姓名提供建议和更正"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"双击空格插入句号"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"双击空格键可插入句号并后跟空格"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自动大写"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"句首字词大写"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"个人词典"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"附加词典"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"主词典"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"显示更正建议"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"始终显示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"在纵向模式中显示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"始终隐藏"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"屏蔽不文明的字词"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"屏蔽可能不文明的字词"</string>
     <string name="auto_correction" msgid="7630720885194996950">"自动更正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空格键和标点可自动更正错别字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"关闭"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"确定要为<xliff:g id="LOCALE_NAME">%s</xliff:g>安装此文件吗?"</string>
     <string name="error" msgid="8940763624668513648">"出现错误"</string>
     <string name="button_default" msgid="3988017840431881491">"默认"</string>
-    <string name="language_settings" msgid="1671153053201809031">"语言和输入法"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"选择输入法"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"欢迎使用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"体验顺畅的滑行输入体验"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"开始"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"下一步"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"设置 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"启用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"请在“语言和输入法”设置中选中“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”,授权这项应用在您的设备上运行。"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"您已在“语言和输入法”设置中启用了 <xliff:g id="APPLICATION_NAME">%s</xliff:g>,因此这一步骤已完成。继续下一步吧!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"在设置中启用"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"切换到 <xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"接下来,请选择“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”作为您要使用的文字输入法。"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"切换输入法"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"恭喜,您已完成了设置!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"现在,您可以在自己喜欢的所有应用中使用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>来输入内容了。"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"配置其他语言"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"完成"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"显示应用图标"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"在启动器中显示应用图标"</string>
     <string name="app_name" msgid="6320102637491234792">"词典提供程序"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"词典提供程序"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"词典服务"</string>
@@ -197,10 +213,30 @@
     <string name="install_dict" msgid="180852772562189365">"安装"</string>
     <string name="cancel_download_dict" msgid="7843340278507019303">"取消"</string>
     <string name="delete_dict" msgid="756853268088330054">"删除"</string>
-    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"支持您移动设备上所选语言的词典现已可供下载啦!&lt;br/&gt;建议您&lt;b&gt;下载&lt;/b&gt;这部<xliff:g id="LANGUAGE">%1$s</xliff:g>词典,以享受更好的输入体验。&lt;br/&gt;&lt;br/&gt;通过 3G 进行下载可能需要 1 到 2 分钟的时间。如果您使用的不是&lt;b&gt;无流量限制的套餐&lt;/b&gt;,则可能需要支付一定的费用。&lt;br/&gt;如果您不确定自己使用的是哪种流量套餐,建议您使用 Wi-Fi 连接自动开始下载。&lt;br/&gt;&lt;br/&gt;提示:您可以访问移动设备的&lt;b&gt;设置&lt;/b&gt;菜单中的&lt;b&gt;语言和输入法&lt;/b&gt;,来下载和删除词典。"</string>
+    <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"支持您移动设备上所选语言的词典现已可供下载啦!&lt;br/&gt;建议您&lt;b&gt;下载&lt;/b&gt;这部<xliff:g id="LANGUAGE">%1$s</xliff:g>词典,以享受更好的输入体验。&lt;br/&gt;&lt;br/&gt;通过 3G 进行下载可能需要 1 到 2 分钟的时间。如果您使用的不是&lt;b&gt;无流量限制的套餐&lt;/b&gt;,则可能需要支付一定的费用。&lt;br/&gt;如果您不确定自己使用的是哪种流量套餐,建议您使用 WLAN 连接自动开始下载。&lt;br/&gt;&lt;br/&gt;提示:您可以访问移动设备的&lt;b&gt;设置&lt;/b&gt;菜单中的&lt;b&gt;语言和输入法&lt;/b&gt;,来下载和删除词典。"</string>
     <string name="download_over_metered" msgid="1643065851159409546">"立即下载 (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>MB)"</string>
-    <string name="do_not_download_over_metered" msgid="2176209579313941583">"通过 Wi-Fi 下载"</string>
+    <string name="do_not_download_over_metered" msgid="2176209579313941583">"通过 WLAN 下载"</string>
     <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>词典可供下载"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"按此通知即可查看和下载"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"下载中:很快就能启用<xliff:g id="LANGUAGE">%1$s</xliff:g>的词典建议服务了!"</string>
+    <string name="version_text" msgid="2715354215568469385">"版本<xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"添加"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"添加到词典"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"词组"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"更多选项"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"隐藏部分选项"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"确定"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"字词:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"快捷键:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"语言:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"输入字词"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"快捷键(选填)"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"修改字词"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"修改"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"删除"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"用户词典中没有您定义的任何字词。您可以触摸“添加”(+) 按钮添加字词。"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"所有语言"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"更多语言…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"删除"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-zh-rTW/dictionary-pack.xml b/java/res/values-zh-rTW/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-zh-rTW/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-zh-rTW/strings-appname.xml
similarity index 64%
rename from java/res/values-af/dictionary-pack.xml
rename to java/res/values-zh-rTW/strings-appname.xml
index f65d45b..8b2a417 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-zh-rTW/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Android 鍵盤 (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Android 拼字檢查 (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Android 鍵盤設定 (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Android 拼字檢查設定 (AOSP)"</string>
 </resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 26a26c7..fe7f8f6 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"不延遲"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"預設"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g> 毫秒"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"系統預設"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"建議聯絡人名稱"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"根據「聯絡人」名稱提供建議與修正"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"輕按兩下空格鍵即插入句號"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"輕按兩下空格鍵可插入句號另加一個空格"</string>
     <string name="auto_cap" msgid="1719746674854628252">"自動大寫"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"句首字詞大寫"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"個人字典"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"外掛字典"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"主要字典"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"顯示修正建議"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"一律顯示"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"在垂直模式中顯示"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"永遠隱藏"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"封鎖令人反感的字詞"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"不建議可能令人反感的字詞"</string>
     <string name="auto_correction" msgid="7630720885194996950">"自動修正"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"按空白鍵或標點符號時,自動修正前面的錯字"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"關閉"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"準備為<xliff:g id="LOCALE_NAME">%s</xliff:g>版本安裝這個檔案嗎?"</string>
     <string name="error" msgid="8940763624668513648">"發生錯誤"</string>
     <string name="button_default" msgid="3988017840431881491">"預設"</string>
-    <string name="language_settings" msgid="1671153053201809031">"語言與輸入設定"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"選擇輸入法"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"歡迎使用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"含手勢輸入功能"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"開始設定"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"下一步"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"正在設定「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"啟用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"請在語言與輸入設定中勾選「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」,授權這項應用程式在您的裝置上執行。"</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"您已在 [語言與輸入設定] 中啟用 <xliff:g id="APPLICATION_NAME">%s</xliff:g>,因此這個步驟已經完成。請進行下一個步驟!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"在設定中啟用"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"切換至「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」"</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"接著,請選取「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」做為目前使用的文字輸入法。"</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"切換輸入法"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"恭喜,您已完成設定!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"現在,您可以在自己喜愛的所有應用程式中使用「<xliff:g id="APPLICATION_NAME">%s</xliff:g>」輸入文字。"</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"設定其他語言"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"完成"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"顯示應用程式圖示"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"在啟動器中顯示應用程式圖示"</string>
     <string name="app_name" msgid="6320102637491234792">"字典提供者"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"字典提供者"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"字典服務"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"支援<xliff:g id="LANGUAGE">%1$s</xliff:g>字典"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"按下即可查看並下載"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"下載中:即將啟用<xliff:g id="LANGUAGE">%1$s</xliff:g>字詞建議服務。"</string>
+    <string name="version_text" msgid="2715354215568469385">"版本 <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"新增"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"加入字典"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"詞組"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"更多選項"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"較少選項"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"確定"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"字詞:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"快速鍵:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"語言:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"輸入字詞"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"可選用的快速鍵"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"編輯字詞"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"編輯"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"刪除"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"您的使用者字典中沒有任何字詞。如要新增字詞,請輕觸「新增」(+) 按鈕。"</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"所有語言"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"更多語言…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"刪除"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values-zu/dictionary-pack.xml b/java/res/values-zu/dictionary-pack.xml
deleted file mode 100644
index f65d45b..0000000
--- a/java/res/values-zu/dictionary-pack.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?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 xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
-</resources>
diff --git a/java/res/values-af/dictionary-pack.xml b/java/res/values-zu/strings-appname.xml
similarity index 62%
copy from java/res/values-af/dictionary-pack.xml
copy to java/res/values-zu/strings-appname.xml
index f65d45b..1aa40f4 100644
--- a/java/res/values-af/dictionary-pack.xml
+++ b/java/res/values-zu/strings-appname.xml
@@ -20,11 +20,8 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for dictionary_pack_settings_activity (664691545147898274) -->
-    <skip />
-    <!-- no translation found for authority (8773166495153016489) -->
-    <skip />
-    <string name="default_metadata_uri" msgid="6889596349847015153"></string>
-    <!-- no translation found for local_metadata_filename (4634356913689271331) -->
-    <skip />
+    <string name="english_ime_name" msgid="5940510615957428904">"Ikhibhodi ye-Android (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Isihloli sokupela se-Android (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Izilungiselelo zekhibhodi ye-Android (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Izilungiselelo zesihloli sokupela se-Android (AOSP)"</string>
 </resources>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 146627f..21cf1db 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -20,10 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <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>
@@ -47,12 +43,14 @@
     <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Cha ukulibazisa"</string>
     <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"Okuzenzakalelayo"</string>
     <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>ms"</string>
+    <string name="settings_system_default" msgid="6268225104743331821">"Okuzenzakalelayo kwesistimu"</string>
     <string name="use_contacts_dict" msgid="4435317977804180815">"Sikisela amagama Othintana nabo"</string>
     <string name="use_contacts_dict_summary" msgid="6599983334507879959">"Amagama abasebenzisi kusuka Kothintana nabo bokusikisela nokulungisa"</string>
     <string name="use_double_space_period" msgid="8781529969425082860">"Isikhathi se-Double-space"</string>
     <string name="use_double_space_period_summary" msgid="6532892187247952799">"Ukuthepha kabili kubha yesikhala kufaka isikhathi esilandelwa yisikhala"</string>
     <string name="auto_cap" msgid="1719746674854628252">"Ukwenza ofeleba okuzenzakalelayo"</string>
     <string name="auto_cap_summary" msgid="7934452761022946874">"Yenza ufeleba wegama lokuqala lomusho ngamunye"</string>
+    <string name="edit_personal_dictionary" msgid="3996910038952940420">"Isichazamazwi somuntu"</string>
     <string name="configure_dictionaries_title" msgid="4238652338556902049">"Faka izichazamazwi"</string>
     <string name="main_dictionary" msgid="4798763781818361168">"Isichazamazwi sakho ngqangi"</string>
     <string name="prefs_show_suggestions" msgid="8026799663445531637">"Bonisa ukusikesela kokulungisa"</string>
@@ -60,6 +58,8 @@
     <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"Bonisa njalo"</string>
     <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"Bonisa ngomumo oqondile"</string>
     <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"Fihla njalo"</string>
+    <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"Vimba amagama ahlaselayo"</string>
+    <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"Ungaphakamisi amagama angaba nokuhlaselayo"</string>
     <string name="auto_correction" msgid="7630720885194996950">"Ukulungisa okuzenzakalelayo"</string>
     <string name="auto_correction_summary" msgid="5625751551134658006">"Ibha yesikhala nokubhala ngamagama amakhulu kulungisa amaphutha amagama athayiphwe kabi"</string>
     <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"Valiwe"</string>
@@ -171,8 +171,24 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Ufuna ukufakela i-<xliff:g id="LOCALE_NAME">%s</xliff:g> leli fayela ngokweqiniso?"</string>
     <string name="error" msgid="8940763624668513648">"Kube nephutha"</string>
     <string name="button_default" msgid="3988017840431881491">"Okuzenzakalelayo"</string>
-    <string name="language_settings" msgid="1671153053201809031">"Ulimi nokokufakwayo"</string>
-    <string name="select_input_method" msgid="4301602374609275003">"Khetha indlela yokufaka"</string>
+    <string name="setup_welcome_title" msgid="6112821709832031715">"Siyakwamukela ku-<xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_welcome_additional_description" msgid="8150252008545768953">"nokuthayipha ngokuthinta"</string>
+    <string name="setup_start_action" msgid="8936036460897347708">"Qalisa"</string>
+    <string name="setup_next_action" msgid="371821437915144603">"Isinyathelo esilandelayo"</string>
+    <string name="setup_steps_title" msgid="6400373034871816182">"Kusethwa i-<xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_title" msgid="3147967630253462315">"Nika amandla i-<xliff:g id="APPLICATION_NAME">%s</xliff:g>"</string>
+    <string name="setup_step1_instruction" msgid="2578631936624637241">"Sicela uhlole i-\"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" ngolimi lwakho nezilungiselelo zokokufaka. Lokhu kuzoyigunyaza ukuthi isebenze kudivayisi yakho."</string>
+    <string name="setup_step1_finished_instruction" msgid="10761482004957994">"I-<xliff:g id="APPLICATION_NAME">%s</xliff:g> isivele inikwe amandla kulimi lwakho nakuzilungiselelo zokufaka, ngakho-ke lesi sinyathelo senziwe. Qhubekela kwesilandelayo!"</string>
+    <string name="setup_step1_action" msgid="4366513534999901728">"Nika amandla kuzilungiselelo"</string>
+    <string name="setup_step2_title" msgid="6860725447906690594">"Shintshela ku-<xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step2_instruction" msgid="9141481964870023336">"Okulandelayo, khetha i-\"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" njengendlela yakho yokufaka umbhalo osebenzayo."</string>
+    <string name="setup_step2_action" msgid="1660330307159824337">"Shintsha izindlela zokufaka"</string>
+    <string name="setup_step3_title" msgid="3154757183631490281">"Siyakuhalalisela, usumi ngomumo!"</string>
+    <string name="setup_step3_instruction" msgid="8025981829605426000">"Manje usungathayipha ngokufaka zonke izinhlelo zokusebenza eziyizintandokazi zakho nge-<xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string>
+    <string name="setup_step3_action" msgid="600879797256942259">"Lungiselela izilimi ezingeziwe"</string>
+    <string name="setup_finish_action" msgid="276559243409465389">"Iqedile"</string>
+    <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Bonisa isithonjana sohlelo lokusebenza"</string>
+    <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"Bonisa isithonjana sohlelo lokusebenza kusiqalisi"</string>
     <string name="app_name" msgid="6320102637491234792">"Umhlinzeki wesichazamazwi"</string>
     <string name="dictionary_provider_name" msgid="3027315045397363079">"Umhlinzeki wesichazamazwi"</string>
     <string name="dictionary_service_name" msgid="6237472350693511448">"Isevisi yesichazamazwi"</string>
@@ -203,4 +219,24 @@
     <string name="dict_available_notification_title" msgid="6514288591959117288">"Isichazamazwi se-<xliff:g id="LANGUAGE">%1$s</xliff:g> siyatholakala"</string>
     <string name="dict_available_notification_description" msgid="1075194169443163487">"Cindezela ukuze ubuyekeze uphinde ulande"</string>
     <string name="toast_downloading_suggestions" msgid="1313027353588566660">"Ukulanda: iziphakamiso ze-<xliff:g id="LANGUAGE">%1$s</xliff:g> zizolunga maduze."</string>
+    <string name="version_text" msgid="2715354215568469385">"Inguqulo engu-<xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string>
+    <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"Engeza"</string>
+    <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"Faka kusichazamazwi"</string>
+    <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"Umshwana"</string>
+    <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"Izinketho eziningi"</string>
+    <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"Izinketho ezincane"</string>
+    <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"KULUNGILE"</string>
+    <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"Igama:"</string>
+    <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"Isinqamulelo:"</string>
+    <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"Ulimi:"</string>
+    <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"Thayipha igama"</string>
+    <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"Ukunqamulela okukhethekayo"</string>
+    <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"Hlela igama"</string>
+    <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"Hlela"</string>
+    <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"Susa"</string>
+    <string name="user_dict_settings_empty_text" msgid="558499587532668203">"Awunawo amagama kwisichazamazwi somsebenzisi. Ungafaka igama ngokuthinta inkinobho yokufaka (+)."</string>
+    <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"Okwazo zonke izilimi"</string>
+    <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"Izilimi eziningi…"</string>
+    <string name="user_dict_settings_delete" msgid="110413335187193859">"Susa"</string>
+    <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string>
 </resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 478a5c0..c318317 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -161,7 +161,7 @@
 
     <declare-styleable name="SuggestionStripView">
         <attr name="suggestionStripOption" format="integer">
-            <!-- This should be aligned with SuggestionStripViewParams.AUTO_CORRECT_* and etc. -->
+            <!-- This should be aligned with SuggestionStripLayoutHelper.AUTO_CORRECT_* and etc. -->
             <flag name="autoCorrectBold" value="0x01" />
             <flag name="autoCorrectUnderline" value="0x02" />
             <flag name="validTypedWordBold" value="0x04" />
@@ -439,7 +439,6 @@
     </declare-styleable>
 
     <declare-styleable name="SeekBarDialogPreference">
-        <attr name="valueFormatText" format="reference" />
         <attr name="maxValue" format="integer" />
         <attr name="minValue" format="integer" />
         <attr name="stepValue" format="integer" />
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index d2554ee..5e990ed 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -21,9 +21,9 @@
     <!-- Symbols that are suggested between words -->
     <string name="suggested_punctuations">!,?,\\,,:,;,\",(,),\',-,/,@,_</string>
     <!-- Symbols that are normally preceded by a space (used to add an auto-space before these) -->
-    <string name="symbols_preceded_by_space">([{*&amp;</string>
+    <string name="symbols_preceded_by_space">([{&amp;</string>
     <!-- Symbols that are normally followed by a space (used to add an auto-space after these) -->
-    <string name="symbols_followed_by_space">.,;:!?)]}*&amp;</string>
+    <string name="symbols_followed_by_space">.,;:!?)]}&amp;</string>
     <!-- Symbols that separate words -->
     <!-- Don't remove the enclosing double quotes, they protect whitespace (not just U+0020) -->
     <string name="symbols_word_separators">"&#x0009;&#x0020;\n"()[]{}*&amp;&lt;&gt;+=|.,;:!?/_\"</string>
diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml
index e8d9225..53448c3 100644
--- a/java/res/values/keypress-vibration-durations.xml
+++ b/java/res/values/keypress-vibration-durations.xml
@@ -51,12 +51,13 @@
         <!-- HTC One X -->
         <item>MODEL=HTC One X:MANUFACTURER=HTC,20</item>
         <!-- HTC One -->
-        <item>MODEL=HTC One( special edition)?:MANUFACTURER=HTC,15</item>
+        <item>MODEL=HTC One:MANUFACTURER=HTC,15</item>
+        <item>MODEL=HTL22:MANUFACTURER=HTC,15</item>
         <!-- Motorola Razor M -->
         <item>MODEL=XT907:MANUFACTURER=motorola,30</item>
         <!-- Sony Xperia Z -->
         <item>MODEL=C6603:MANUFACTURER=Sony,35</item>
-        <!-- Default value for unknown device -->
-        <item>,20</item>
+        <!-- Default value for unknown device. The negative value means system default. -->
+        <item>,-1</item>
     </string-array>
 </resources>
diff --git a/java/res/values/keypress-volumes.xml b/java/res/values/keypress-volumes.xml
index a096c34..d359055 100644
--- a/java/res/values/keypress-volumes.xml
+++ b/java/res/values/keypress-volumes.xml
@@ -26,7 +26,7 @@
         <item>HARDWARE=grouper,0.3f</item>
         <item>HARDWARE=mako,0.3f</item>
         <item>HARDWARE=manta,0.2f</item>
-        <!-- Default value for unknown device -->
-        <item>,0.2f</item>
+        <!-- Default value for unknown device. The negative value means system default. -->
+        <item>,-1.0f</item>
     </string-array>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 8b31181..a7d2bd9 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -79,6 +79,8 @@
 
     <!-- Units abbreviation for the duration (milliseconds) [CHAR LIMIT=10] -->
     <string name="abbreviation_unit_milliseconds"><xliff:g id="milliseconds">%s</xliff:g>ms</string>
+    <!-- The text that represents the current settings value is the system default [CHAR LIMIT=25] -->
+    <string name="settings_system_default">System default</string>
 
     <!-- Option name for enabling or disabling the use of names of people in Contacts for suggestion and correction [CHAR LIMIT=25] -->
     <string name="use_contacts_dict">Suggest Contact names</string>
@@ -426,6 +428,8 @@
     <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>
+    <!-- Title of the settings for using only personalization dictionary -->
+    <string name="prefs_use_only_personalization_dictionary" translatable="false">Use only personalization dictionary</string>
     <!-- Message to show when there are no files to install as an external dictionary [CHAR LIMIT=100] -->
     <string name="read_external_dictionary_no_files_message">No dictionary files in the Downloads folder</string>
     <!-- Title of the dialog that selects a file to install as an external dictionary [CHAR LIMIT=50] -->
@@ -509,9 +513,9 @@
     <!-- Message about some dictionary indicating the file is installed, but the dictionary is disabled -->
     <string name="dictionary_disabled">Installed, disabled</string>
 
-    <!-- Message to display in the dictionaries setting screen when some error prevented us to list installed dictionaries [CHAR LIMIT=50] -->
+    <!-- Message to display in the dictionaries setting screen when some error prevented us to list installed dictionaries [CHAR LIMIT=20] -->
     <string name="cannot_connect_to_dict_service">Problem connecting to dictionary service</string>
-    <!-- Message to display in the dictionaries setting screen when we found that no dictionaries are available [CHAR LIMIT=50]-->
+    <!-- Message to display in the dictionaries setting screen when we found that no dictionaries are available [CHAR LIMIT=20]-->
     <string name="no_dictionaries_available">No dictionaries available</string>
 
     <!-- Title of the options to press to refresh the list (as in, check for updates now) [CHAR_LIMIT=50] -->
diff --git a/java/res/values/sudden-jumping-touch-event-device-list.xml b/java/res/values/sudden-jumping-touch-event-device-list.xml
deleted file mode 100644
index 3a9c379..0000000
--- a/java/res/values/sudden-jumping-touch-event-device-list.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2011, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-<resources>
-    <string-array name="sudden_jumping_touch_event_device_list" translatable="false">
-        <!-- "Build condition,true" that needs "sudden jump touch event" hack.
-             See {@link com.android.inputmethod.keyboard.SuddenJumpingTouchEventHandler}. -->
-        <!-- Nexus One -->
-        <item>HARDWARE=mahimahi,true</item>
-        <!-- Droid -->
-        <item>HARDWARE=sholes,true</item>
-        <!-- Default value for unknown device -->
-        <item>,false</item>
-    </string-array>
-</resources>
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml-sw600dp/key_azerty3_right.xml
similarity index 90%
rename from java/res/xml-sw600dp/key_azerty_quote.xml
rename to java/res/xml-sw600dp/key_azerty3_right.xml
index 0e4a8ec..a5a6e95 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml-sw600dp/key_azerty3_right.xml
@@ -22,8 +22,8 @@
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
+        latin:keyLabel=":"
+        latin:keyHintLabel=";"
+        latin:moreKeys=";"
         latin:keyStyle="hasShiftedLetterHintStyle" />
 </merge>
diff --git a/java/res/xml-sw600dp/key_styles_common.xml b/java/res/xml-sw600dp/key_styles_common.xml
index 6b06ce7..a2d2fd8 100644
--- a/java/res/xml-sw600dp/key_styles_common.xml
+++ b/java/res/xml-sw600dp/key_styles_common.xml
@@ -36,34 +36,38 @@
         </default>
     </switch>
     <!-- Functional key styles -->
+    <!-- Base style for shift key. A single space is used for dummy label in moreKeys. -->
+    <key-style
+        latin:styleName="baseForShiftKeyStyle"
+        latin:code="!code/key_shift"
+        latin:keyActionFlags="noKeyPreview"
+        latin:keyLabelFlags="preserveCase"
+        latin:moreKeys="!noPanelAutoMoreKey!, |!code/key_capslock" />
     <switch>
         <case
             latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetAutomaticShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <case
             latin:keyboardLayoutSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOn" />
+                latin:backgroundType="stickyOn"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <default>
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </default>
     </switch>
     <key-style
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml-sw600dp/keys_arabic3_left.xml
similarity index 69%
copy from java/res/xml-sw600dp/key_azerty_quote.xml
copy to java/res/xml-sw600dp/keys_arabic3_left.xml
index 0e4a8ec..0f2ccc0 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml-sw600dp/keys_arabic3_left.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -21,9 +21,12 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
+        latin:keyLabel="&#x0630;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0626;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-sw600dp/keys_dvorak_123.xml b/java/res/xml-sw600dp/keys_dvorak_123.xml
index 635ea04..58416ab 100644
--- a/java/res/xml-sw600dp/keys_dvorak_123.xml
+++ b/java/res/xml-sw600dp/keys_dvorak_123.xml
@@ -21,20 +21,39 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel="&quot;"
-        latin:moreKeys="!"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
-    <Key
-        latin:keyLabel=","
-        latin:keyHintLabel="&lt;"
-        latin:moreKeys="\?"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
-    <Key
-        latin:keyLabel="."
-        latin:keyHintLabel="&gt;"
-        latin:keyLabelFlags="hasPopupHint|preserveCase"
-        latin:moreKeys="!text/more_keys_for_punctuation"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
+    <switch>
+        <case
+            latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
+        >
+            <Key
+                latin:keyLabel="&quot;"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1" />
+            <Key
+                latin:keyLabel="&lt;"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+            <Key
+                latin:keyLabel="&gt;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3" />
+        </case>
+        <default>
+            <Key
+                latin:keyLabel="\'"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="!,&quot;" />
+            <Key
+                latin:keyLabel=","
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="\?,&lt;" />
+            <Key
+                latin:keyLabel="."
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3"
+                latin:moreKeys="&gt;" />
+        </default>
+    </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml-sw600dp/keys_farsi3_right.xml
similarity index 69%
copy from java/res/xml-sw600dp/key_azerty_quote.xml
copy to java/res/xml-sw600dp/keys_farsi3_right.xml
index 0e4a8ec..3c91ae9 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml-sw600dp/keys_farsi3_right.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -21,9 +21,12 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
+        latin:keyLabel="&#x0622;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x0686;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic1.xml b/java/res/xml-sw600dp/rowkeys_arabic1.xml
deleted file mode 100644
index 6a0e257..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic1.xml
+++ /dev/null
@@ -1,82 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0636: "ض" ARABIC LETTER DAD -->
-    <Key
-        latin:keyLabel="&#x0636;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0635: "ص" ARABIC LETTER SAD -->
-    <Key
-        latin:keyLabel="&#x0635;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062B: "ث" ARABIC LETTER THEH -->
-    <Key
-        latin:keyLabel="&#x062B;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF
-         U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-    <Key
-        latin:keyLabel="&#x0642;"
-        latin:moreKeys="&#x06A8;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH
-         U+06A4: "ڤ" ARABIC LETTER VEH
-         U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
-         U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-    <Key
-        latin:keyLabel="&#x0641;"
-        latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN -->
-    <Key
-        latin:keyLabel="&#x0639;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0647: "ه" ARABIC LETTER HEH
-         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
-         U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER -->
-    <Key
-        latin:keyLabel="&#x0647;"
-        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH -->
-    <Key
-        latin:keyLabel="&#x062D;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
-         U+0686: "چ" ARABIC LETTER TCHEH -->
-    <Key
-        latin:keyLabel="&#x062C;"
-        latin:moreKeys="&#x0686;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic2.xml b/java/res/xml-sw600dp/rowkeys_arabic2.xml
deleted file mode 100644
index 00e69ac..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic2.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0634: "ش" ARABIC LETTER SHEEN
-         U+069C: "ڜ" ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-    <Key
-        latin:keyLabel="&#x0634;"
-        latin:moreKeys="&#x069C;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN -->
-    <Key
-        latin:keyLabel="&#x0633;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+064A: "ي" ARABIC LETTER YEH
-         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
-         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x064A;"
-        latin:moreKeys="&#x0626;,&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0628: "ب" ARABIC LETTER BEH
-         U+067E: "پ" ARABIC LETTER PEH -->
-    <Key
-        latin:keyLabel="&#x0628;"
-        latin:moreKeys="&#x067E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0644: "ل" ARABIC LETTER LAM
-         U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
-         U+0627: "ا" ARABIC LETTER ALEF
-         U+FEF7: "ﻷ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+FEF9: "ﻹ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
-         U+FEF5: "ﻵ" ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0644;"
-        latin:moreKeys="&#xFEFB;|&#x0644;&#x0627;,&#xFEF7;|&#x0644;&#x0623;,&#xFEF9;|&#x0644;&#x0625;,&#xFEF5;|&#x0644;&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0627: "ا" ARABIC LETTER ALEF
-         U+0621: "ء" ARABIC LETTER HAMZA
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH -->
-    <Key
-        latin:keyLabel="&#x062A;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0646: "ن" ARABIC LETTER NOON -->
-    <Key
-        latin:keyLabel="&#x0646;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0645: "م" ARABIC LETTER MEEM -->
-    <Key
-        latin:keyLabel="&#x0645;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0643: "ك" ARABIC LETTER KAF
-         U+06AF: "گ" ARABIC LETTER GAF
-         U+06A9: "ک" ARABIC LETTER KEHEH -->
-    <Key
-        latin:keyLabel="&#x0643;"
-        latin:moreKeys="&#x06AF;,&#x06A9;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
-    <Key
-        latin:keyLabel="&#x0637;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic3.xml b/java/res/xml-sw600dp/rowkeys_arabic3.xml
deleted file mode 100644
index b0bcd78..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic3.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0626;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0621: "ء" ARABIC LETTER HAMZA -->
-    <Key
-        latin:keyLabel="&#x0621;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0624;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
-    <Key
-        latin:keyLabel="&#x0631;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
-    <Key
-        latin:keyLabel="&#x0630;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
-    <Key
-        latin:keyLabel="&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN
-         U+0698: "ژ" ARABIC LETTER JEH -->
-    <Key
-        latin:keyLabel="&#x0632;"
-        latin:moreKeys="&#x0698;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
-    <Key
-        latin:keyLabel="&#x0638;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
-    <Key
-        latin:keyLabel="&#x062F;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi1.xml b/java/res/xml-sw600dp/rowkeys_farsi1.xml
deleted file mode 100644
index 7b31240..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi1.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0636: "ض" ARABIC LETTER DAD -->
-    <Key
-        latin:keyLabel="&#x0636;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0635: "ص" ARABIC LETTER SAD -->
-    <Key
-        latin:keyLabel="&#x0635;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062B: "ث" ARABIC LETTER THEH -->
-    <Key
-        latin:keyLabel="&#x062B;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF -->
-    <Key
-        latin:keyLabel="&#x0642;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH -->
-    <Key
-        latin:keyLabel="&#x0641;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN -->
-    <Key
-        latin:keyLabel="&#x0639;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0647: "ه" ARABIC LETTER HEH
-         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
-         U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
-         U+0647/U+0654: ARABIC LETTER HEH + ARABIC HAMZA ABOVE
-         U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
-    <Key
-        latin:keyLabel="&#x0647;"
-        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x0647;&#x0654;,&#x0629;,%"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH -->
-    <Key
-        latin:keyLabel="&#x062D;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM -->
-    <Key
-        latin:keyLabel="&#x062C;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
-    <Key
-        latin:keyLabel="&#x0686;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi2.xml b/java/res/xml-sw600dp/rowkeys_farsi2.xml
deleted file mode 100644
index 3b759b6..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi2.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0634: "ش" ARABIC LETTER SHEEN -->
-    <Key
-        latin:keyLabel="&#x0634;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN -->
-    <Key
-        latin:keyLabel="&#x0633;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
-         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
-         U+064A: "ي" ARABIC LETTER YEH
-         U+FBE8: "ﯨ" ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM
-         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x06CC;"
-        latin:moreKeys="&#x0626;,&#x064A;,&#xFBE8;|&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0628: "ب" ARABIC LETTER BEH -->
-    <Key
-        latin:keyLabel="&#x0628;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0644: "ل" ARABIC LETTER LAM -->
-    <Key
-        latin:keyLabel="&#x0644;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0627: "ا" ARABIC LETTER ALEF
-         U+0621: "ء" ARABIC LETTER HAMZA
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW -->
-    <Key
-        latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0622;,&#x0623;,&#x0671;,&#x0625;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH
-         U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
-    <Key
-        latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;,&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0646: "ن" ARABIC LETTER NOON -->
-    <Key
-        latin:keyLabel="&#x0646;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0645: "م" ARABIC LETTER MEEM -->
-    <Key
-        latin:keyLabel="&#x0645;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06A9: "ک" ARABIC LETTER KEHEH
-         U+0643: "ك" ARABIC LETTER KAF -->
-    <Key
-        latin:keyLabel="&#x06A9;"
-        latin:moreKeys="&#x0643;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
-    <Key
-        latin:keyLabel="&#x06AF;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi3.xml b/java/res/xml-sw600dp/rowkeys_farsi3.xml
deleted file mode 100644
index 3597618..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi3.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
-    <Key
-        latin:keyLabel="&#x0638;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
-    <Key
-        latin:keyLabel="&#x0637;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0698: "ژ" ARABIC LETTER JEH -->
-    <Key
-        latin:keyLabel="&#x0698;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN -->
-    <Key
-        latin:keyLabel="&#x0632;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
-    <Key
-        latin:keyLabel="&#x0631;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
-    <Key
-        latin:keyLabel="&#x0630;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
-    <Key
-        latin:keyLabel="&#x062F;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+067E: "پ" ARABIC LETTER PEH -->
-    <Key
-        latin:keyLabel="&#x067E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW
-         U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:moreKeys="&#x0624;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rows_arabic.xml b/java/res/xml-sw600dp/rows_arabic.xml
index ec7c2ad..5a28d45 100644
--- a/java/res/xml-sw600dp/rows_arabic.xml
+++ b/java/res/xml-sw600dp/rows_arabic.xml
@@ -45,8 +45,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="4.091%p" />
+            latin:keyboardLayout="@xml/rowkeys_arabic3" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_azerty.xml b/java/res/xml-sw600dp/rows_azerty.xml
index 824ee38..5a5a7d1 100644
--- a/java/res/xml-sw600dp/rows_azerty.xml
+++ b/java/res/xml-sw600dp/rows_azerty.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_azerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_azerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_bulgarian.xml b/java/res/xml-sw600dp/rows_bulgarian.xml
index 7253236..2635620 100644
--- a/java/res/xml-sw600dp/rows_bulgarian.xml
+++ b/java/res/xml-sw600dp/rows_bulgarian.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_bulgarian_bds.xml b/java/res/xml-sw600dp/rows_bulgarian_bds.xml
index db6220e..9439a63 100644
--- a/java/res/xml-sw600dp/rows_bulgarian_bds.xml
+++ b/java/res/xml-sw600dp/rows_bulgarian_bds.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_colemak.xml b/java/res/xml-sw600dp/rows_colemak.xml
index f7e903b..98a24e4 100644
--- a/java/res/xml-sw600dp/rows_colemak.xml
+++ b/java/res/xml-sw600dp/rows_colemak.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_colemak1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_colemak1" />
         <include
             latin:keyboardLayout="@xml/key_colemak_colon" />
         <Key
diff --git a/java/res/xml-sw600dp/rows_dvorak.xml b/java/res/xml-sw600dp/rows_dvorak.xml
index 2fa8eb2..8859267 100644
--- a/java/res/xml-sw600dp/rows_dvorak.xml
+++ b/java/res/xml-sw600dp/rows_dvorak.xml
@@ -27,10 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/keys_dvorak_123" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_dvorak1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_dvorak1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_east_slavic.xml b/java/res/xml-sw600dp/rows_east_slavic.xml
index 3096255..b4160d6 100644
--- a/java/res/xml-sw600dp/rows_east_slavic.xml
+++ b/java/res/xml-sw600dp/rows_east_slavic.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_east_slavic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_east_slavic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_farsi.xml b/java/res/xml-sw600dp/rows_farsi.xml
index 52c2d93..a353b67 100644
--- a/java/res/xml-sw600dp/rows_farsi.xml
+++ b/java/res/xml-sw600dp/rows_farsi.xml
@@ -28,12 +28,6 @@
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
-    </Row>
-    <Row
-        latin:keyWidth="8.182%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
@@ -42,12 +36,18 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_farsi3"
-            latin:keyXPos="4.091%p" />
+            latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
+    <Row
+        latin:keyWidth="8.182%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi3"
+            latin:keyXPos="4.091%p" />
+    </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw600dp/rows_georgian.xml b/java/res/xml-sw600dp/rows_georgian.xml
index 61d3eb0..b0e9e35 100644
--- a/java/res/xml-sw600dp/rows_georgian.xml
+++ b/java/res/xml-sw600dp/rows_georgian.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_georgian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_georgian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_greek.xml b/java/res/xml-sw600dp/rows_greek.xml
index 6a10228..de214c6 100644
--- a/java/res/xml-sw600dp/rows_greek.xml
+++ b/java/res/xml-sw600dp/rows_greek.xml
@@ -29,8 +29,7 @@
         <include
             latin:keyboardLayout="@xml/key_greek_semicolon" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_greek1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_greek1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_hebrew.xml b/java/res/xml-sw600dp/rows_hebrew.xml
index f9e6f51..9945dee 100644
--- a/java/res/xml-sw600dp/rows_hebrew.xml
+++ b/java/res/xml-sw600dp/rows_hebrew.xml
@@ -27,10 +27,6 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/key_apostrophe" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
-        <include
             latin:keyboardLayout="@xml/rowkeys_hebrew1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
diff --git a/java/res/xml-sw600dp/rows_hindi.xml b/java/res/xml-sw600dp/rows_hindi.xml
index 9678465..2a9a419 100644
--- a/java/res/xml-sw600dp/rows_hindi.xml
+++ b/java/res/xml-sw600dp/rows_hindi.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_hindi1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_hindi1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_mongolian.xml b/java/res/xml-sw600dp/rows_mongolian.xml
index cfd8c8c..dc0c1fe 100644
--- a/java/res/xml-sw600dp/rows_mongolian.xml
+++ b/java/res/xml-sw600dp/rows_mongolian.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_mongolian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_mongolian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_nordic.xml b/java/res/xml-sw600dp/rows_nordic.xml
index 4314403..299bf89 100644
--- a/java/res/xml-sw600dp/rows_nordic.xml
+++ b/java/res/xml-sw600dp/rows_nordic.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_nordic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_nordic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_qwerty.xml b/java/res/xml-sw600dp/rows_qwerty.xml
index bac02fd..722f9d1 100644
--- a/java/res/xml-sw600dp/rows_qwerty.xml
+++ b/java/res/xml-sw600dp/rows_qwerty.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_qwertz.xml b/java/res/xml-sw600dp/rows_qwertz.xml
index 98ddd08..f2f832c 100644
--- a/java/res/xml-sw600dp/rows_qwertz.xml
+++ b/java/res/xml-sw600dp/rows_qwertz.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwertz1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwertz1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_south_slavic.xml b/java/res/xml-sw600dp/rows_south_slavic.xml
index e53a2ee..6ef6643 100644
--- a/java/res/xml-sw600dp/rows_south_slavic.xml
+++ b/java/res/xml-sw600dp/rows_south_slavic.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_south_slavic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw600dp/rows_spanish.xml b/java/res/xml-sw600dp/rows_spanish.xml
index b48ee01..bca9bba 100644
--- a/java/res/xml-sw600dp/rows_spanish.xml
+++ b/java/res/xml-sw600dp/rows_spanish.xml
@@ -27,8 +27,7 @@
         latin:keyWidth="9.0%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/key_styles_common.xml b/java/res/xml-sw768dp/key_styles_common.xml
index 4d10f5b..e69bc30 100644
--- a/java/res/xml-sw768dp/key_styles_common.xml
+++ b/java/res/xml-sw768dp/key_styles_common.xml
@@ -35,34 +35,38 @@
                 latin:keyLabelFlags="hasShiftedLetterHint" />
         </default>
     </switch>
+    <!-- Base style for shift key. A single space is used for dummy label in moreKeys. -->
+    <key-style
+        latin:styleName="baseForShiftKeyStyle"
+        latin:code="!code/key_shift"
+        latin:keyActionFlags="noKeyPreview"
+        latin:keyLabelFlags="preserveCase"
+        latin:moreKeys="!noPanelAutoMoreKey!, |!code/key_capslock" />
     <switch>
         <case
             latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetAutomaticShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <case
             latin:keyboardLayoutSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOn" />
+                latin:backgroundType="stickyOn"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <default>
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </default>
     </switch>
     <key-style
diff --git a/java/res/xml-sw768dp/rows_arabic.xml b/java/res/xml-sw768dp/rows_arabic.xml
index 8b05d93..204f6d5 100644
--- a/java/res/xml-sw768dp/rows_arabic.xml
+++ b/java/res/xml-sw768dp/rows_arabic.xml
@@ -54,7 +54,7 @@
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="13.829%p" />
+            latin:keyXPos="6.602%p" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw768dp/rows_azerty.xml b/java/res/xml-sw768dp/rows_azerty.xml
index dcc403b..cf4bc92 100644
--- a/java/res/xml-sw768dp/rows_azerty.xml
+++ b/java/res/xml-sw768dp/rows_azerty.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_azerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_azerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_bulgarian.xml b/java/res/xml-sw768dp/rows_bulgarian.xml
index 6453414..bdc1262 100644
--- a/java/res/xml-sw768dp/rows_bulgarian.xml
+++ b/java/res/xml-sw768dp/rows_bulgarian.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_bulgarian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_bulgarian_bds.xml b/java/res/xml-sw768dp/rows_bulgarian_bds.xml
index 8acb4d2..58c4611 100644
--- a/java/res/xml-sw768dp/rows_bulgarian_bds.xml
+++ b/java/res/xml-sw768dp/rows_bulgarian_bds.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_bulgarian_bds1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_colemak.xml b/java/res/xml-sw768dp/rows_colemak.xml
index db9b0c2..073f812 100644
--- a/java/res/xml-sw768dp/rows_colemak.xml
+++ b/java/res/xml-sw768dp/rows_colemak.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_colemak1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_colemak1" />
         <include
             latin:keyboardLayout="@xml/key_colemak_colon" />
         <Key
diff --git a/java/res/xml-sw768dp/rows_dvorak.xml b/java/res/xml-sw768dp/rows_dvorak.xml
index 9416478..60d5dd6 100644
--- a/java/res/xml-sw768dp/rows_dvorak.xml
+++ b/java/res/xml-sw768dp/rows_dvorak.xml
@@ -31,10 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/keys_dvorak_123" />
-        <include
-            latin:keyboardLayout="@xml/rowkeys_dvorak1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_dvorak1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_east_slavic.xml b/java/res/xml-sw768dp/rows_east_slavic.xml
index a4287f1..420307d 100644
--- a/java/res/xml-sw768dp/rows_east_slavic.xml
+++ b/java/res/xml-sw768dp/rows_east_slavic.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_east_slavic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_east_slavic1" />
         <Key
             latin:keyLabel="!text/keylabel_for_east_slavic_row1_12" />
         <Key
diff --git a/java/res/xml-sw768dp/rows_farsi.xml b/java/res/xml-sw768dp/rows_farsi.xml
index 4b4c970..8d3fb05 100644
--- a/java/res/xml-sw768dp/rows_farsi.xml
+++ b/java/res/xml-sw768dp/rows_farsi.xml
@@ -32,6 +32,9 @@
             latin:keyWidth="7.969%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
     </Row>
     <Row
         latin:keyWidth="7.227%p"
@@ -39,22 +42,19 @@
         <Key
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
+            latin:keyWidth="11.172%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
-            latin:keyStyle="deleteKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="7.186%p"
+        latin:keyWidth="7.227%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi3"
             latin:keyXPos="13.829%p" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw768dp/rows_georgian.xml b/java/res/xml-sw768dp/rows_georgian.xml
index 74f1a07..3f8bd45 100644
--- a/java/res/xml-sw768dp/rows_georgian.xml
+++ b/java/res/xml-sw768dp/rows_georgian.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_georgian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_georgian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight"/>
diff --git a/java/res/xml-sw768dp/rows_greek.xml b/java/res/xml-sw768dp/rows_greek.xml
index aebe129..9e1e00b 100644
--- a/java/res/xml-sw768dp/rows_greek.xml
+++ b/java/res/xml-sw768dp/rows_greek.xml
@@ -33,8 +33,7 @@
         <include
             latin:keyboardLayout="@xml/key_greek_semicolon" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_greek1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_greek1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight"/>
diff --git a/java/res/xml-sw768dp/rows_hebrew.xml b/java/res/xml-sw768dp/rows_hebrew.xml
index e588b83..a5f6dfe 100644
--- a/java/res/xml-sw768dp/rows_hebrew.xml
+++ b/java/res/xml-sw768dp/rows_hebrew.xml
@@ -31,10 +31,6 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/key_apostrophe" />
-        <include
-            latin:keyboardLayout="@xml/key_dash" />
-        <include
             latin:keyboardLayout="@xml/rowkeys_hebrew1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
diff --git a/java/res/xml-sw768dp/rows_hindi.xml b/java/res/xml-sw768dp/rows_hindi.xml
index 510772b..6baf09e 100644
--- a/java/res/xml-sw768dp/rows_hindi.xml
+++ b/java/res/xml-sw768dp/rows_hindi.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_hindi1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_hindi1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_mongolian.xml b/java/res/xml-sw768dp/rows_mongolian.xml
index 9afd4e2..5f37f87 100644
--- a/java/res/xml-sw768dp/rows_mongolian.xml
+++ b/java/res/xml-sw768dp/rows_mongolian.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_mongolian1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_mongolian1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_nordic.xml b/java/res/xml-sw768dp/rows_nordic.xml
index 06591a6..13d9399 100644
--- a/java/res/xml-sw768dp/rows_nordic.xml
+++ b/java/res/xml-sw768dp/rows_nordic.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_nordic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_nordic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_qwerty.xml b/java/res/xml-sw768dp/rows_qwerty.xml
index a1deabd..8af18ed 100644
--- a/java/res/xml-sw768dp/rows_qwerty.xml
+++ b/java/res/xml-sw768dp/rows_qwerty.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight"/>
diff --git a/java/res/xml-sw768dp/rows_qwertz.xml b/java/res/xml-sw768dp/rows_qwertz.xml
index 801dd38..0dd206d 100644
--- a/java/res/xml-sw768dp/rows_qwertz.xml
+++ b/java/res/xml-sw768dp/rows_qwertz.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwertz1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwertz1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight"/>
diff --git a/java/res/xml-sw768dp/rows_south_slavic.xml b/java/res/xml-sw768dp/rows_south_slavic.xml
index b556853..6b44c4e 100644
--- a/java/res/xml-sw768dp/rows_south_slavic.xml
+++ b/java/res/xml-sw768dp/rows_south_slavic.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_south_slavic1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_south_slavic1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml-sw768dp/rows_spanish.xml b/java/res/xml-sw768dp/rows_spanish.xml
index 8b80332..4520c10 100644
--- a/java/res/xml-sw768dp/rows_spanish.xml
+++ b/java/res/xml-sw768dp/rows_spanish.xml
@@ -31,8 +31,7 @@
             latin:keyLabelFlags="alignLeft"
             latin:keyWidth="7.969%p" />
         <include
-            latin:keyboardLayout="@xml/rowkeys_qwerty1"
-            latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" />
+            latin:keyboardLayout="@xml/rowkeys_qwerty1" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight"/>
diff --git a/java/res/xml/key_azerty_quote.xml b/java/res/xml/key_azerty3_right.xml
similarity index 100%
rename from java/res/xml/key_azerty_quote.xml
rename to java/res/xml/key_azerty3_right.xml
diff --git a/java/res/xml/key_styles_common.xml b/java/res/xml/key_styles_common.xml
index 0834adf..6590d0a 100644
--- a/java/res/xml/key_styles_common.xml
+++ b/java/res/xml/key_styles_common.xml
@@ -25,34 +25,38 @@
     <include
         latin:keyboardLayout="@xml/key_styles_f1" />
     <!-- Functional key styles -->
+    <!-- Base style for shift key. A single space is used for dummy label in moreKeys. -->
+    <key-style
+        latin:styleName="baseForShiftKeyStyle"
+        latin:code="!code/key_shift"
+        latin:keyActionFlags="noKeyPreview"
+        latin:keyLabelFlags="preserveCase"
+        latin:moreKeys="!noPanelAutoMoreKey!, |!code/key_capslock" />
     <switch>
         <case
             latin:keyboardLayoutSetElement="alphabetManualShifted|alphabetAutomaticShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <case
             latin:keyboardLayoutSetElement="alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key_shifted"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOn" />
+                latin:backgroundType="stickyOn"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </case>
         <default>
             <key-style
                 latin:styleName="shiftKeyStyle"
-                latin:code="!code/key_shift"
                 latin:keyIcon="!icon/shift_key"
-                latin:keyActionFlags="noKeyPreview"
-                latin:backgroundType="stickyOff" />
+                latin:backgroundType="stickyOff"
+                latin:parentStyle="baseForShiftKeyStyle" />
         </default>
     </switch>
     <key-style
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml/keys_arabic3_left.xml
similarity index 78%
copy from java/res/xml-sw600dp/key_azerty_quote.xml
copy to java/res/xml/keys_arabic3_left.xml
index 0e4a8ec..157af4a 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml/keys_arabic3_left.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -21,9 +21,8 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
+        latin:keyLabel="&#x0630;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/keys_dvorak_123.xml b/java/res/xml/keys_dvorak_123.xml
index 60e6b6f..fa94f1f 100644
--- a/java/res/xml/keys_dvorak_123.xml
+++ b/java/res/xml/keys_dvorak_123.xml
@@ -51,7 +51,7 @@
                 latin:keyLabel="\'"
                 latin:keyHintLabel="1"
                 latin:additionalMoreKeys="1"
-                latin:moreKeys="!" />
+                latin:moreKeys="!,&quot;" />
         </default>
     </switch>
     <switch>
@@ -72,13 +72,12 @@
                 latin:keyLabel=","
                 latin:keyHintLabel="2"
                 latin:additionalMoreKeys="2"
-                latin:moreKeys="\?" />
+                latin:moreKeys="\?,&lt;" />
             <Key
                 latin:keyLabel="."
                 latin:keyHintLabel="3"
                 latin:additionalMoreKeys="3"
-                latin:keyLabelFlags="hasPopupHint|preserveCase"
-                latin:moreKeys="!text/more_keys_for_punctuation,%" />
+                latin:moreKeys="&gt;" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml-sw600dp/key_azerty_quote.xml b/java/res/xml/keys_farsi3_right.xml
similarity index 78%
copy from java/res/xml-sw600dp/key_azerty_quote.xml
copy to java/res/xml/keys_farsi3_right.xml
index 0e4a8ec..77efb0a 100644
--- a/java/res/xml-sw600dp/key_azerty_quote.xml
+++ b/java/res/xml/keys_farsi3_right.xml
@@ -2,7 +2,7 @@
 <!--
 /*
 **
-** Copyright 2012, The Android Open Source Project
+** 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.
@@ -21,9 +21,8 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
     <Key
-        latin:keyLabel="\'"
-        latin:keyHintLabel=":"
-        latin:moreKeys=":"
-        latin:keyStyle="hasShiftedLetterHintStyle" />
+        latin:keyLabel="&#x0686;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index f30ef23..52d715a 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -21,9 +21,10 @@
 <!-- for the Input Method Manager. -->
 
 <!-- Supported subtypes
-    keyboard_locale: script_name/keyboard_layout_set[:keyboard_locale]
+    keyboard_locale: script_name/keyboard_layout_set
     af: Afrikaans/qwerty
     ar: Arabic/arabic
+    (az: Azerbaijani/qwerty) # disabled temporarily. waiting for strnig resources.
     be: Belarusian/east_slavic
     bg: Bulgarian/bulgarian
     bg: Bulgarian/bulgarian_bds
@@ -51,6 +52,7 @@
     it: Italian/qwerty
     iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
     ka: Georgian/georgian
+    (kk: Kazakh/east_slavic) # disabled temporarily. waiting for strnig resources.
     ky: Kyrgyz/east_slavic
     lt: Lithuanian/qwerty
     lv: Latvian/qwerty
@@ -85,7 +87,7 @@
 <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default
      subtype.-->
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
-        android:settingsActivity="com.android.inputmethod.latin.SettingsActivity"
+        android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity"
         android:isDefault="@bool/im_is_default">
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_en_US"
@@ -115,6 +117,15 @@
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="SupportTouchPositionCorrection"
     />
+    <!--
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:subtypeId="0x70b0f974"
+            android:imeSubtypeLocale="az"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable"
+    />
+    -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:subtypeId="0x1dc3a859"
@@ -294,6 +305,15 @@
             android:imeSubtypeMode="keyboard"
             android:imeSubtypeExtraValue="KeyboardLayoutSet=georgian"
     />
+    <!--
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
+            android:subtypeId="0x2d73d2f6"
+            android:imeSubtypeLocale="kk"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+    />
+    -->
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
             android:subtypeId="0x2e391c04"
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 51e3420..2a726c4 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -161,25 +161,23 @@
                 android:persistent="true"
                 android:defaultValue="true" />
             <PreferenceScreen
-                android:fragment="com.android.inputmethod.latin.AdditionalSubtypeSettings"
+                android:fragment="com.android.inputmethod.latin.settings.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" />
-            <com.android.inputmethod.latin.SeekBarDialogPreference
+            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
                 android:key="pref_key_longpress_timeout"
                 android:title="@string/prefs_key_longpress_timeout_settings"
-                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
                 latin:minValue="@integer/config_min_longpress_timeout"
                 latin:maxValue="@integer/config_max_longpress_timeout"
                 latin:stepValue="@integer/config_longpress_timeout_step" />
-            <com.android.inputmethod.latin.SeekBarDialogPreference
+            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
                 android:key="pref_vibration_duration_settings"
                 android:title="@string/prefs_keypress_vibration_duration_settings"
-                latin:valueFormatText="@string/abbreviation_unit_milliseconds"
                 latin:maxValue="@integer/config_max_vibration_duration" />
-            <com.android.inputmethod.latin.SeekBarDialogPreference
+            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
                 android:key="pref_keypress_sound_volume"
                 android:title="@string/prefs_keypress_sound_volume_settings"
                 latin:maxValue="100" /> <!-- percent -->
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 8efd1c9..5d89b9c 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -51,6 +51,12 @@
             android:persistent="true"
             android:defaultValue="false" />
 
+    <CheckBoxPreference
+        android:defaultValue="false"
+        android:key="use_only_personalization_dictionary_for_debug"
+        android:persistent="true"
+        android:title="@string/prefs_use_only_personalization_dictionary" />
+
     <PreferenceScreen
         android:key="read_external_dictionary"
         android:title="@string/prefs_read_external_dictionary" />
diff --git a/java/res/xml/row_dvorak4.xml b/java/res/xml/row_dvorak4.xml
index 69bac13..e6d487e 100644
--- a/java/res/xml/row_dvorak4.xml
+++ b/java/res/xml/row_dvorak4.xml
@@ -68,7 +68,8 @@
             latin:keyboardLayout="@xml/key_space" />
         <Key
             latin:keyLabel="z"
-            latin:moreKeys="!text/more_keys_for_z" />
+            latin:keyLabelFlags="hasPopupHint"
+            latin:moreKeys="!text/more_keys_for_punctuation,!text/more_keys_for_z" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
diff --git a/java/res/xml/rowkeys_arabic1.xml b/java/res/xml/rowkeys_arabic1.xml
index a4bef83..3c0acf1 100644
--- a/java/res/xml/rowkeys_arabic1.xml
+++ b/java/res/xml/rowkeys_arabic1.xml
@@ -35,74 +35,78 @@
         latin:keyHintLabel="2"
         latin:additionalMoreKeys="2,&#x0662;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+062B: "ث" ARABIC LETTER THEH
+         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x062B;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3,&#x0663;"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0642: "ق" ARABIC LETTER QAF
          U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE
-         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
     <Key
         latin:keyLabel="&#x0642;"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="3,&#x0663;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4,&#x0664;"
         latin:moreKeys="&#x06A8;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0641: "ف" ARABIC LETTER FEH
          U+06A4: "ڤ" ARABIC LETTER VEH
          U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
          U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW
-         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
+         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
     <Key
         latin:keyLabel="&#x0641;"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4,&#x0664;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5,&#x0665;"
         latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+063A: "غ" ARABIC LETTER GHAIN
-         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="5,&#x0665;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
          U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
     <Key
-        latin:keyLabel="&#x0639;"
+        latin:keyLabel="&#x063A;"
         latin:keyHintLabel="6"
         latin:additionalMoreKeys="6,&#x0666;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7,&#x0667;"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0647: "ه" ARABIC LETTER HEH
          U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
          U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
-         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
     <Key
         latin:keyLabel="&#x0647;"
-        latin:keyHintLabel="7"
-        latin:additionalMoreKeys="7,&#x0667;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8,&#x0668;"
         latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="8"
-        latin:additionalMoreKeys="8,&#x0668;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
     <Key
-        latin:keyLabel="&#x062D;"
+        latin:keyLabel="&#x062E;"
         latin:keyHintLabel="9"
         latin:additionalMoreKeys="9,&#x0669;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
-         U+0686: "چ" ARABIC LETTER TCHEH
+    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
     <Key
-        latin:keyLabel="&#x062C;"
+        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="0"
         latin:additionalMoreKeys="0,&#x0660;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM
+         U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x062C;"
         latin:moreKeys="&#x0686;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_arabic2.xml b/java/res/xml/rowkeys_arabic2.xml
index d733f64..4f8090d 100644
--- a/java/res/xml/rowkeys_arabic2.xml
+++ b/java/res/xml/rowkeys_arabic2.xml
@@ -59,20 +59,18 @@
         latin:moreKeys="&#xFEFB;|&#x0644;&#x0627;,&#xFEF7;|&#x0644;&#x0623;,&#xFEF9;|&#x0644;&#x0625;,&#xFEF5;|&#x0644;&#x0622;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
          U+0621: "ء" ARABIC LETTER HAMZA
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
          U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
          U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA -->
     <Key
         latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;"
+        latin:moreKeys="!fixedColumnOrder!5,&#x0622;,&#x0621;,&#x0623;,&#x0625;,&#x0671;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH -->
+    <!-- U+062A: "ت" ARABIC LETTER TEH -->
     <Key
         latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0646: "ن" ARABIC LETTER NOON -->
     <Key
@@ -89,4 +87,8 @@
         latin:keyLabel="&#x0643;"
         latin:moreKeys="&#x06AF;,&#x06A9;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <Key
+        latin:keyLabel="&#x0637;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_arabic3.xml b/java/res/xml/rowkeys_arabic3.xml
index e4e6948..8a17b4b 100644
--- a/java/res/xml/rowkeys_arabic3.xml
+++ b/java/res/xml/rowkeys_arabic3.xml
@@ -21,21 +21,33 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <include
+        latin:keyboardLayout="@xml/keys_arabic3_left" />
+    <!-- U+0621: "ء" ARABIC LETTER HAMZA -->
     <Key
-        latin:keyLabel="&#x0638;"
+        latin:keyLabel="&#x0621;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
     <Key
-        latin:keyLabel="&#x0637;"
+        latin:keyLabel="&#x0624;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
     <Key
-        latin:keyLabel="&#x0630;"
+        latin:keyLabel="&#x0631;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
+    <!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
     <Key
-        latin:keyLabel="&#x062F;"
+        latin:keyLabel="&#x0649;"
+        latin:moreKeys="&#x0626;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <Key
+        latin:keyLabel="&#x0629;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0648: "و" ARABIC LETTER WAW -->
+    <Key
+        latin:keyLabel="&#x0648;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0632: "ز" ARABIC LETTER ZAIN
          U+0698: "ژ" ARABIC LETTER JEH -->
@@ -43,18 +55,12 @@
         latin:keyLabel="&#x0632;"
         latin:moreKeys="&#x0698;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
     <Key
-        latin:keyLabel="&#x0631;"
+        latin:keyLabel="&#x0638;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <!-- U+062F: "د" ARABIC LETTER DAL -->
     <Key
-        latin:keyLabel="&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW
-         U+0624: "ﺅ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:moreKeys="&#x0624;"
+        latin:keyLabel="&#x062F;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_azerty3.xml b/java/res/xml/rowkeys_azerty3.xml
index 9f4c608..2643f32 100644
--- a/java/res/xml/rowkeys_azerty3.xml
+++ b/java/res/xml/rowkeys_azerty3.xml
@@ -38,5 +38,5 @@
         latin:keyLabel="n"
         latin:moreKeys="!text/more_keys_for_n" />
     <include
-        latin:keyboardLayout="@xml/key_azerty_quote" />
+        latin:keyboardLayout="@xml/key_azerty3_right" />
 </merge>
diff --git a/java/res/xml/rowkeys_dvorak1.xml b/java/res/xml/rowkeys_dvorak1.xml
index 7e0eb6e..033308a 100644
--- a/java/res/xml/rowkeys_dvorak1.xml
+++ b/java/res/xml/rowkeys_dvorak1.xml
@@ -21,6 +21,8 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <include
+        latin:keyboardLayout="@xml/keys_dvorak_123" />
     <Key
         latin:keyLabel="p"
         latin:keyHintLabel="4"
diff --git a/java/res/xml/rowkeys_east_slavic1.xml b/java/res/xml/rowkeys_east_slavic1.xml
index c1b43bd..5b3b4b4 100644
--- a/java/res/xml/rowkeys_east_slavic1.xml
+++ b/java/res/xml/rowkeys_east_slavic1.xml
@@ -41,7 +41,8 @@
     <Key
         latin:keyLabel="&#x043A;"
         latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4" />
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="!text/more_keys_for_cyrillic_ka" />
     <!-- U+0435: "е" CYRILLIC SMALL LETTER IE -->
     <Key
         latin:keyLabel="&#x0435;"
diff --git a/java/res/xml/rowkeys_east_slavic2.xml b/java/res/xml/rowkeys_east_slavic2.xml
index 9743727..2e412f0 100644
--- a/java/res/xml/rowkeys_east_slavic2.xml
+++ b/java/res/xml/rowkeys_east_slavic2.xml
@@ -32,7 +32,8 @@
         latin:keyLabel="&#x0432;" />
     <!-- U+0430: "а" CYRILLIC SMALL LETTER A -->
     <Key
-        latin:keyLabel="&#x0430;" />
+        latin:keyLabel="&#x0430;"
+        latin:moreKeys="!text/more_keys_for_cyrillic_a" />
     <!-- U+043F: "п" CYRILLIC SMALL LETTER PE -->
     <Key
         latin:keyLabel="&#x043F;" />
@@ -53,5 +54,6 @@
     <Key
         latin:keyLabel="&#x0436;" />
     <Key
-        latin:keyLabel="!text/keylabel_for_east_slavic_row2_11" />
+        latin:keyLabel="!text/keylabel_for_east_slavic_row2_11"
+        latin:moreKeys="!text/more_keys_for_east_slavic_row2_11" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi1.xml b/java/res/xml/rowkeys_farsi1.xml
index 0ccf1ab..5a22a24 100644
--- a/java/res/xml/rowkeys_farsi1.xml
+++ b/java/res/xml/rowkeys_farsi1.xml
@@ -21,81 +21,83 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0635: "ص" ARABIC LETTER SAD
-         U+0636: "ض" ARABIC LETTER DAD
+    <!-- U+0636: "ض" ARABIC LETTER DAD
          U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE -->
     <Key
-        latin:keyLabel="&#x0635;"
-        latin:moreKeys="&#x0636;,%"
+        latin:keyLabel="&#x0636;"
         latin:keyHintLabel="&#x06F1;"
         latin:additionalMoreKeys="&#x06F1;,1"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF
+    <!-- U+0635: "ص" ARABIC LETTER SAD
          U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
     <Key
-        latin:keyLabel="&#x0642;"
+        latin:keyLabel="&#x0635;"
         latin:keyHintLabel="&#x06F2;"
         latin:additionalMoreKeys="&#x06F2;,2"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH
+    <!-- U+062B: "ث" ARABIC LETTER THEH
          U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
     <Key
-        latin:keyLabel="&#x0641;"
+        latin:keyLabel="&#x062B;"
         latin:keyHintLabel="&#x06F3;"
         latin:additionalMoreKeys="&#x06F3;,3"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+    <!-- U+0642: "ق" ARABIC LETTER QAF
          U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
     <Key
-        latin:keyLabel="&#x063A;"
+        latin:keyLabel="&#x0642;"
         latin:keyHintLabel="&#x06F4;"
         latin:additionalMoreKeys="&#x06F4;,4"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
+    <!-- U+0641: "ف" ARABIC LETTER FEH
          U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
     <Key
-        latin:keyLabel="&#x0639;"
+        latin:keyLabel="&#x0641;"
         latin:keyHintLabel="&#x06F5;"
         latin:additionalMoreKeys="&#x06F5;,5"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+    <Key
+        latin:keyLabel="&#x063A;"
+        latin:keyHintLabel="&#x06F6;"
+        latin:additionalMoreKeys="&#x06F6;,6"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="&#x06F7;"
+        latin:additionalMoreKeys="&#x06F7;,7"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0647: "ه" ARABIC LETTER HEH
          U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
          U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
          U+0647/U+0654: ARABIC LETTER HEH + ARABIC HAMZA ABOVE
          U+0629: "ة" ARABIC LETTER TEH MARBUTA
-         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
     <Key
         latin:keyLabel="&#x0647;"
         latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x0647;&#x0654;,&#x0629;,%"
-        latin:keyHintLabel="&#x06F6;"
-        latin:additionalMoreKeys="&#x06F6;,6"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="&#x06F7;"
-        latin:additionalMoreKeys="&#x06F7;,7"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
-         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="&#x06F8;"
         latin:additionalMoreKeys="&#x06F8;,8"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
+    <!-- U+062E: "خ" ARABIC LETTER KHAH
          U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
     <Key
-        latin:keyLabel="&#x062C;"
+        latin:keyLabel="&#x062E;"
         latin:keyHintLabel="&#x06F9;"
         latin:additionalMoreKeys="&#x06F9;,9"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0686: "چ" ARABIC LETTER TCHEH
+    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
     <Key
-        latin:keyLabel="&#x0686;"
+        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="&#x06F0;"
         latin:additionalMoreKeys="&#x06F0;,0"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM -->
+    <Key
+        latin:keyLabel="&#x062C;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi2.xml b/java/res/xml/rowkeys_farsi2.xml
index 4b6abe2..590161f 100644
--- a/java/res/xml/rowkeys_farsi2.xml
+++ b/java/res/xml/rowkeys_farsi2.xml
@@ -25,11 +25,9 @@
     <Key
         latin:keyLabel="&#x0634;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN
-         U+0636: "ض" ARABIC LETTER DAD -->
+    <!-- U+0633: "س" ARABIC LETTER SEEN -->
     <Key
         latin:keyLabel="&#x0633;"
-        latin:moreKeys="&#x0636;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
          U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
@@ -49,21 +47,20 @@
         latin:keyLabel="&#x0644;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0627: "ا" ARABIC LETTER ALEF
+         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
          U+0621: "ء" ARABIC LETTER HAMZA
          U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
          U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
          U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW -->
     <Key
         latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0622;,&#x0623;,&#x0671;,&#x0625;"
+        latin:moreKeys="!fixedColumnOrder!5,&#x0671;,&#x0621;,&#x0622;,&#x0623;,&#x0625;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH
          U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
     <Key
         latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;,&#x0629;"
+        latin:moreKeys="&#x0629;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0646: "ن" ARABIC LETTER NOON -->
     <Key
@@ -79,4 +76,8 @@
         latin:keyLabel="&#x06A9;"
         latin:moreKeys="&#x0643;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
+    <Key
+        latin:keyLabel="&#x06AF;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi3.xml b/java/res/xml/rowkeys_farsi3.xml
index 7d2e81f..98949f4 100644
--- a/java/res/xml/rowkeys_farsi3.xml
+++ b/java/res/xml/rowkeys_farsi3.xml
@@ -21,17 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0637: "ط" ARABIC LETTER TAH
-         U+0638: "ظ" ARABIC LETTER ZAH -->
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0638;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
     <Key
         latin:keyLabel="&#x0637;"
-        latin:moreKeys="&#x0638;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN
-         U+0698: "ژ" ARABIC LETTER JEH -->
+    <!-- U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0698;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN -->
     <Key
         latin:keyLabel="&#x0632;"
-        latin:moreKeys="&#x0698;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0631: "ر" ARABIC LETTER REH -->
     <Key
@@ -55,8 +59,6 @@
         latin:keyLabel="&#x0648;"
         latin:moreKeys="&#x0624;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
-    <Key
-        latin:keyLabel="&#x06AF;"
-        latin:keyLabelFlags="fontNormal" />
+    <include
+        latin:keyboardLayout="@xml/keys_farsi3_right" />
 </merge>
diff --git a/java/res/xml/rowkeys_hebrew1.xml b/java/res/xml/rowkeys_hebrew1.xml
index 396da78..81a00e3 100644
--- a/java/res/xml/rowkeys_hebrew1.xml
+++ b/java/res/xml/rowkeys_hebrew1.xml
@@ -21,28 +21,70 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
+    <switch>
+        <case
+            latin:mode="email|url"
+        >
+            <Key
+                latin:keyLabel="-"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1" />
+            <Key
+                latin:keyLabel="_"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2" />
+        </case>
+        <default>
+            <Key
+                latin:keyLabel="\'"
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&quot;" />
+            <Key
+                latin:keyLabel="-"
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="_" />
+        </default>
+    </switch>
     <!-- U+05E7: "ק" HEBREW LETTER QOF -->
     <Key
-        latin:keyLabel="&#x05E7;" />
+        latin:keyLabel="&#x05E7;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3" />
     <!-- U+05E8: "ר" HEBREW LETTER RESH -->
     <Key
-        latin:keyLabel="&#x05E8;" />
+        latin:keyLabel="&#x05E8;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4" />
     <!-- U+05D0: "א" HEBREW LETTER ALEF -->
     <Key
-        latin:keyLabel="&#x05D0;" />
+        latin:keyLabel="&#x05D0;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5" />
     <!-- U+05D8: "ט" HEBREW LETTER TET -->
     <Key
-        latin:keyLabel="&#x05D8;" />
+        latin:keyLabel="&#x05D8;"
+        latin:keyHintLabel="6"
+        latin:additionalMoreKeys="6" />
     <!-- U+05D5: "ו" HEBREW LETTER VAV -->
     <Key
-        latin:keyLabel="&#x05D5;" />
+        latin:keyLabel="&#x05D5;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7" />
     <!-- U+05DF: "ן" HEBREW LETTER FINAL NUN -->
     <Key
-        latin:keyLabel="&#x05DF;" />
+        latin:keyLabel="&#x05DF;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8" />
     <!-- U+05DD: "ם" HEBREW LETTER FINAL MEM -->
     <Key
-        latin:keyLabel="&#x05DD;" />
+        latin:keyLabel="&#x05DD;"
+        latin:keyHintLabel="9"
+        latin:additionalMoreKeys="9" />
     <!-- U+05E4: "פ" HEBREW LETTER PE -->
     <Key
-        latin:keyLabel="&#x05E4;" />
+        latin:keyLabel="&#x05E4;"
+        latin:keyHintLabel="0"
+        latin:additionalMoreKeys="0" />
 </merge>
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
index a761a6c..7457476 100644
--- a/java/res/xml/rowkeys_hindi1.xml
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -123,7 +123,7 @@
             <Key
                 latin:keyLabel="&#x0926;"
                 latin:keyHintLabel="9"
-                latin:additionalMoreKeys="9"
+                latin:additionalMoreKeys="&#x096F;,9"
                 latin:keyLabelFlags="fontNormal" />
             <!-- U+091C: "ज" DEVANAGARI LETTER JA
                  U+091C/U+0952: "ज॒" DEVANAGARI LETTER JA/DEVANAGARI STRESS SIGN ANUDATTA
diff --git a/java/res/xml/rowkeys_thai1.xml b/java/res/xml/rowkeys_thai1.xml
index 950d2a4..cd53665 100644
--- a/java/res/xml/rowkeys_thai1.xml
+++ b/java/res/xml/rowkeys_thai1.xml
@@ -81,17 +81,33 @@
             <Key
                 latin:keyLabel="&#x0E45;"
                 latin:keyLabelFlags="fontNormal" />
+            <!-- U+0E51: "๑" THAI DIGIT ONE -->
             <Key
+                latin:keyHintLabel="1"
+                latin:additionalMoreKeys="1"
+                latin:moreKeys="&#x0E51;"
                 latin:keyLabel="/" />
+            <!-- U+0E52: "๒" THAI DIGIT TWO -->
             <Key
+                latin:keyHintLabel="2"
+                latin:additionalMoreKeys="2"
+                latin:moreKeys="&#x0E52;"
                 latin:keyLabel="_" />
-            <!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO -->
+            <!-- U+0E20: "ภ" THAI CHARACTER PHO SAMPHAO
+                 U+0E53: "๓" THAI DIGIT THREE -->
             <Key
                 latin:keyLabel="&#x0E20;"
+                latin:keyHintLabel="3"
+                latin:additionalMoreKeys="3"
+                latin:moreKeys="&#x0E53;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0E16: "ถ" THAI CHARACTER THO THUNG -->
+            <!-- U+0E16: "ถ" THAI CHARACTER THO THUNG
+                 U+0E54: "๔" THAI DIGIT FOUR -->
             <Key
                 latin:keyLabel="&#x0E16;"
+                latin:keyHintLabel="4"
+                latin:additionalMoreKeys="4"
+                latin:moreKeys="&#x0E54;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- U+0020: " " SPACE
                  U+0E38: " ุ" THAI CHARACTER SARA U -->
@@ -109,25 +125,45 @@
                 latin:keyLabel="&#x20;&#x0E36;"
                 latin:code="0x0E36"
                 latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
-            <!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI -->
+            <!-- U+0E04: "ค" THAI CHARACTER KHO KHWAI
+                 U+0E55: "๕" THAI DIGIT FIVE -->
             <Key
                 latin:keyLabel="&#x0E04;"
+                latin:keyHintLabel="5"
+                latin:additionalMoreKeys="5"
+                latin:moreKeys="&#x0E55;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0E15: "ต" THAI CHARACTER TO TAO -->
+            <!-- U+0E15: "ต" THAI CHARACTER TO TAO
+                 U+0E56: "๖" THAI DIGIT SIX -->
             <Key
                 latin:keyLabel="&#x0E15;"
+                latin:keyHintLabel="6"
+                latin:additionalMoreKeys="6"
+                latin:moreKeys="&#x0E56;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0E08: "จ" THAI CHARACTER CHO CHAN -->
+            <!-- U+0E08: "จ" THAI CHARACTER CHO CHAN
+                 U+0E57: "๗" THAI DIGIT SEVEN -->
             <Key
                 latin:keyLabel="&#x0E08;"
+                latin:keyHintLabel="7"
+                latin:additionalMoreKeys="7"
+                latin:moreKeys="&#x0E57;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0E02: "ข" THAI CHARACTER KHO KHAI -->
+            <!-- U+0E02: "ข" THAI CHARACTER KHO KHAI
+                 U+0E58: "๘" THAI DIGIT EIGHT -->
             <Key
                 latin:keyLabel="&#x0E02;"
+                latin:keyHintLabel="8"
+                latin:additionalMoreKeys="8"
+                latin:moreKeys="&#x0E58;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG -->
+            <!-- U+0E0A: "ช" THAI CHARACTER CHO CHANG
+                 U+0E59: "๙" THAI DIGIT NINE -->
             <Key
                 latin:keyLabel="&#x0E0A;"
+                latin:keyHintLabel="9"
+                latin:additionalMoreKeys="9"
+                latin:moreKeys="&#x0E59;"
                 latin:keyLabelFlags="fontNormal" />
         </default>
     </switch>
diff --git a/java/res/xml/rowkeys_thai2.xml b/java/res/xml/rowkeys_thai2.xml
index f602994..4bcbbbf 100644
--- a/java/res/xml/rowkeys_thai2.xml
+++ b/java/res/xml/rowkeys_thai2.xml
@@ -79,9 +79,13 @@
                 latin:keyLabel="," />
         </case>
         <default>
-            <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK -->
+            <!-- U+0E46: "ๆ" THAI CHARACTER MAIYAMOK
+                 U+0E50: "๐" THAI DIGIT ZERO -->
             <Key
                 latin:keyLabel="&#x0E46;"
+                latin:keyHintLabel="0"
+                latin:additionalMoreKeys="0"
+                latin:moreKeys="&#x0E50;"
                 latin:keyLabelFlags="fontNormal" />
             <!-- U+0E44: "ไ" THAI CHARACTER SARA AI MAIMALAI -->
             <Key
diff --git a/java/res/xml/rows_arabic.xml b/java/res/xml/rows_arabic.xml
index 6449af2..798c23e 100644
--- a/java/res/xml/rows_arabic.xml
+++ b/java/res/xml/rows_arabic.xml
@@ -24,27 +24,25 @@
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic1" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic2" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="5.0%p" />
+            latin:keyboardLayout="@xml/rowkeys_arabic3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/rows_dvorak.xml b/java/res/xml/rows_dvorak.xml
index 8e3d071..13d7021 100644
--- a/java/res/xml/rows_dvorak.xml
+++ b/java/res/xml/rows_dvorak.xml
@@ -27,8 +27,6 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/keys_dvorak_123" />
-        <include
             latin:keyboardLayout="@xml/rowkeys_dvorak1" />
         </Row>
     <Row
diff --git a/java/res/xml/rows_farsi.xml b/java/res/xml/rows_farsi.xml
index cc0c526..c74614f 100644
--- a/java/res/xml/rows_farsi.xml
+++ b/java/res/xml/rows_farsi.xml
@@ -24,27 +24,25 @@
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi2" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_farsi3"
-            latin:keyXPos="5.0%p" />
+            latin:keyboardLayout="@xml/rowkeys_farsi3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/rows_hebrew.xml b/java/res/xml/rows_hebrew.xml
index 2d513df..f12380a 100644
--- a/java/res/xml/rows_hebrew.xml
+++ b/java/res/xml/rows_hebrew.xml
@@ -27,12 +27,7 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew1"
-            latin:keyXPos="5%p" />
-        <Key
-            latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyboardLayout="@xml/rowkeys_hebrew1" />
     </Row>
     <Row
         latin:keyWidth="10%p"
@@ -44,8 +39,10 @@
         latin:keyWidth="10%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_hebrew3"
-            latin:keyXPos="5%p" />
+            latin:keyboardLayout="@xml/rowkeys_hebrew3" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml
index b77544b..291018a 100644
--- a/java/res/xml/rows_number_normal.xml
+++ b/java/res/xml/rows_number_normal.xml
@@ -117,7 +117,7 @@
                 <Key
                     latin:code="0x002F"
                     latin:keyLabel="/ :"
-                    latin:moreKeys="!embeddedMoreKey!,:"
+                    latin:moreKeys="!noPanelAutoMoreKey!,:"
                     latin:keyStyle="numKeyStyle" />
             </case>
             <default>
diff --git a/java/res/xml/rows_phone.xml b/java/res/xml/rows_phone.xml
index 9299c2a..d8dcfbd 100644
--- a/java/res/xml/rows_phone.xml
+++ b/java/res/xml/rows_phone.xml
@@ -70,7 +70,7 @@
             latin:keyStyle="num0KeyStyle"
             latin:code="0x0030"
             latin:keyLabel="0 +"
-            latin:moreKeys="!embeddedMoreKey!,+" />
+            latin:moreKeys="!noPanelAutoMoreKey!,+" />
         <Key
             latin:keyStyle="numSpaceKeyStyle" />
         <Key
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 0576f66..77f3234 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -35,8 +35,8 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardView;
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.CoordinateUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 
 /**
  * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index ee52de1..8929dc7 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -33,8 +33,8 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.compat.SettingsSecureCompatUtils;
-import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
 
 public final class AccessibilityUtils {
     private static final String TAG = AccessibilityUtils.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index 05d8269..41f5b9a 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -25,9 +25,9 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
diff --git a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
index df2e22f..965a2a8 100644
--- a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
@@ -21,16 +21,15 @@
 public final class IntentCompatUtils {
     // Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17
     // (Build.VERSION_CODE.JELLY_BEAN_MR1).
-    public static final String ACTION_USER_INITIALIZE =
-            (String)CompatUtils.getFieldValue(null, null,
+    private static final String ACTION_USER_INITIALIZE =
+            (String)CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
                     CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE"));
 
     private IntentCompatUtils() {
         // This utility class is not publicly instantiable.
     }
 
-    public static boolean has_ACTION_USER_INITIALIZE(final Intent intent) {
-        return ACTION_USER_INITIALIZE != null && intent != null
-                && ACTION_USER_INITIALIZE.equals(intent.getAction());
+    public static boolean is_ACTION_USER_INITIALIZE(final String action) {
+        return ACTION_USER_INITIALIZE != null && ACTION_USER_INITIALIZE.equals(action);
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
index 141e08a..55282c5 100644
--- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
+++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java
@@ -23,17 +23,15 @@
 import android.text.TextUtils;
 import android.text.style.SuggestionSpan;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
 
 public final class SuggestionSpanUtils {
-    private static final String TAG = SuggestionSpanUtils.class.getSimpleName();
-
     // Note that SuggestionSpan.FLAG_AUTO_CORRECTION has been introduced
     // in API level 15 (Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1).
     public static final Field FIELD_FLAG_AUTO_CORRECTION = CompatUtils.getField(
@@ -60,7 +58,7 @@
         }
         final Spannable spannable = new SpannableString(text);
         final SuggestionSpan suggestionSpan = new SuggestionSpan(context, null /* locale */,
-                new String[] {} /* suggestions */, (int)OBJ_FLAG_AUTO_CORRECTION,
+                new String[] {} /* suggestions */, OBJ_FLAG_AUTO_CORRECTION,
                 SuggestionSpanPickedNotificationReceiver.class);
         spannable.setSpan(suggestionSpan, 0, text.length(),
                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index bf22305..d5e638e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -28,6 +28,8 @@
 
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
 
 import java.util.LinkedList;
 import java.util.Queue;
@@ -98,7 +100,7 @@
         final boolean mForceStartNow;
         public StartDownloadAction(final String clientId,
                 final WordListMetadata wordList, final boolean forceStartNow) {
-            Utils.l("New download action for client ", clientId, " : ", wordList);
+            DebugLogUtils.l("New download action for client ", clientId, " : ", wordList);
             mClientId = clientId;
             mWordList = wordList;
             mForceStartNow = forceStartNow;
@@ -110,7 +112,7 @@
                 Log.e(TAG, "UpdateAction with a null parameter!");
                 return;
             }
-            Utils.l("Downloading word list");
+            DebugLogUtils.l("Downloading word list");
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -132,7 +134,7 @@
                         + " for an upgrade action. Fall back to download.");
             }
             // Download it.
-            Utils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
+            DebugLogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename);
 
             // TODO: if DownloadManager is disabled or not installed, download by ourselves
             if (null == manager) return;
@@ -142,7 +144,7 @@
             // DownloadManager also stupidly cuts the extension to replace with its own that it
             // gets from the content-type. We need to circumvent this.
             final String disambiguator = "#" + System.currentTimeMillis()
-                    + com.android.inputmethod.latin.Utils.getVersionName(context) + ".dict";
+                    + ApplicationUtils.getVersionName(context) + ".dict";
             final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
             final Request request = new Request(uri);
 
@@ -178,7 +180,7 @@
 
             final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db,
                     mWordList.mId, mWordList.mVersion);
-            Utils.l("Starting download of", uri, "with id", downloadId);
+            DebugLogUtils.l("Starting download of", uri, "with id", downloadId);
             PrivateLog.log("Starting download of " + uri + ", id : " + downloadId);
         }
     }
@@ -195,7 +197,8 @@
 
         public InstallAfterDownloadAction(final String clientId,
                 final ContentValues wordListValues) {
-            Utils.l("New InstallAfterDownloadAction for client ", clientId, " : ", wordListValues);
+            DebugLogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ",
+                    wordListValues);
             mClientId = clientId;
             mWordListValues = wordListValues;
         }
@@ -213,7 +216,7 @@
                         + " for an InstallAfterDownload action. Bailing out.");
                 return;
             }
-            Utils.l("Setting word list as installed");
+            DebugLogUtils.l("Setting word list as installed");
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues);
         }
@@ -229,7 +232,7 @@
         final WordListMetadata mWordList;
 
         public EnableAction(final String clientId, final WordListMetadata wordList) {
-            Utils.l("New EnableAction for client ", clientId, " : ", wordList);
+            DebugLogUtils.l("New EnableAction for client ", clientId, " : ", wordList);
             mClientId = clientId;
             mWordList = wordList;
         }
@@ -240,7 +243,7 @@
                 Log.e(TAG, "EnableAction with a null parameter!");
                 return;
             }
-            Utils.l("Enabling word list");
+            DebugLogUtils.l("Enabling word list");
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -264,7 +267,7 @@
         // The word list to disable. May not be null.
         final WordListMetadata mWordList;
         public DisableAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New Disable action for client ", clientId, " : ", wordlist);
+            DebugLogUtils.l("New Disable action for client ", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -275,7 +278,7 @@
                 Log.e(TAG, "DisableAction with a null word list!");
                 return;
             }
-            Utils.l("Disabling word list : " + mWordList);
+            DebugLogUtils.l("Disabling word list : " + mWordList);
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -311,7 +314,7 @@
         // The word list to make available. May not be null.
         final WordListMetadata mWordList;
         public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New MakeAvailable action", clientId, " : ", wordlist);
+            DebugLogUtils.l("New MakeAvailable action", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -328,7 +331,7 @@
                 Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
                         + " for a makeavailable action. Marking as available anyway.");
             }
-            Utils.l("Making word list available : " + mWordList);
+            DebugLogUtils.l("Making word list available : " + mWordList);
             // If mLocalFilename is null, then it's a remote file that hasn't been downloaded
             // yet, so we set the local filename to the empty string.
             final ContentValues values = MetadataDbHelper.makeContentValues(0,
@@ -360,7 +363,7 @@
         // The word list to mark pre-installed. May not be null.
         final WordListMetadata mWordList;
         public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
+            DebugLogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -377,7 +380,7 @@
                 Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' "
                         + " for a markpreinstalled action. Marking as preinstalled anyway.");
             }
-            Utils.l("Marking word list preinstalled : " + mWordList);
+            DebugLogUtils.l("Marking word list preinstalled : " + mWordList);
             // This word list is pre-installed : we don't have its file. We should reset
             // the local file name to the empty string so that we don't try to open it
             // accidentally. The remote filename may be set by the application if it so wishes.
@@ -401,7 +404,7 @@
         private final String mClientId;
         final WordListMetadata mWordList;
         public UpdateDataAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New UpdateData action for client ", clientId, " : ", wordlist);
+            DebugLogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -419,7 +422,7 @@
                 Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out.");
                 return;
             }
-            Utils.l("Updating data about a word list : " + mWordList);
+            DebugLogUtils.l("Updating data about a word list : " + mWordList);
             final ContentValues values = MetadataDbHelper.makeContentValues(
                     oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN),
                     oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN),
@@ -453,7 +456,7 @@
         final boolean mHasNewerVersion;
         public ForgetAction(final String clientId, final WordListMetadata wordlist,
                 final boolean hasNewerVersion) {
-            Utils.l("New TryRemove action for client ", clientId, " : ", wordlist);
+            DebugLogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
             mHasNewerVersion = hasNewerVersion;
@@ -465,7 +468,7 @@
                 Log.e(TAG, "TryRemoveAction with a null word list!");
                 return;
             }
-            Utils.l("Trying to remove word list : " + mWordList);
+            DebugLogUtils.l("Trying to remove word list : " + mWordList);
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -525,7 +528,7 @@
         // The word list to delete. May not be null.
         final WordListMetadata mWordList;
         public StartDeleteAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New StartDelete action for client ", clientId, " : ", wordlist);
+            DebugLogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -536,7 +539,7 @@
                 Log.e(TAG, "StartDeleteAction with a null word list!");
                 return;
             }
-            Utils.l("Trying to delete word list : " + mWordList);
+            DebugLogUtils.l("Trying to delete word list : " + mWordList);
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -564,7 +567,7 @@
         // The word list to delete. May not be null.
         final WordListMetadata mWordList;
         public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) {
-            Utils.l("New FinishDelete action for client", clientId, " : ", wordlist);
+            DebugLogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist);
             mClientId = clientId;
             mWordList = wordlist;
         }
@@ -575,7 +578,7 @@
                 Log.e(TAG, "FinishDeleteAction with a null word list!");
                 return;
             }
-            Utils.l("Trying to delete word list : " + mWordList);
+            DebugLogUtils.l("Trying to delete word list : " + mWordList);
             final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId);
             final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db,
                     mWordList.mId, mWordList.mVersion);
@@ -632,7 +635,7 @@
      * @param reporter a Reporter to send errors to.
      */
     public void execute(final Context context, final ProblemReporter reporter) {
-        Utils.l("Executing a batch of actions");
+        DebugLogUtils.l("Executing a batch of actions");
         Queue<Action> remainingActions = mActions;
         while (!remainingActions.isEmpty()) {
             final Action a = remainingActions.poll();
diff --git a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
index 5ab94a4..6d6c8f5 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ButtonSwitcher.java
@@ -47,6 +47,7 @@
     private Button mInstallButton;
     private Button mCancelButton;
     private Button mDeleteButton;
+    private DictionaryListInterfaceState mInterfaceState;
     private OnClickListener mOnClickListener;
 
     public ButtonSwitcher(Context context, AttributeSet attrs) {
@@ -57,6 +58,12 @@
         super(context, attrs, defStyle);
     }
 
+    public void reset(final DictionaryListInterfaceState interfaceState) {
+        mStatus = NOT_INITIALIZED;
+        mAnimateToStatus = NOT_INITIALIZED;
+        mInterfaceState = interfaceState;
+    }
+
     @Override
     protected void onLayout(final boolean changed, final int left, final int top, final int right,
             final int bottom) {
@@ -64,9 +71,7 @@
         mInstallButton = (Button)findViewById(R.id.dict_install_button);
         mCancelButton = (Button)findViewById(R.id.dict_cancel_button);
         mDeleteButton = (Button)findViewById(R.id.dict_delete_button);
-        mInstallButton.setOnClickListener(mOnClickListener);
-        mCancelButton.setOnClickListener(mOnClickListener);
-        mDeleteButton.setOnClickListener(mOnClickListener);
+        setInternalOnClickListener(mOnClickListener);
         setButtonPositionWithoutAnimation(mStatus);
         if (mAnimateToStatus != NOT_INITIALIZED) {
             // We have been asked to animate before we were ready, so we took a note of it.
@@ -139,11 +144,18 @@
 
     public void setInternalOnClickListener(final OnClickListener listener) {
         mOnClickListener = listener;
+        if (null != mInstallButton) {
+            // Already laid out : do it now
+            mInstallButton.setOnClickListener(mOnClickListener);
+            mCancelButton.setOnClickListener(mOnClickListener);
+            mDeleteButton.setOnClickListener(mOnClickListener);
+        }
     }
 
     private ViewPropertyAnimator animateButton(final View button, final int direction) {
         final float outerX = getWidth();
         final float innerX = button.getX() - button.getTranslationX();
+        mInterfaceState.removeFromCache((View)getParent());
         if (ANIMATION_IN == direction) {
             button.setClickable(true);
             return button.animate().translationX(0);
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
index de3711c..13c07de 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java
@@ -16,8 +16,11 @@
 
 package com.android.inputmethod.dictionarypack;
 
-import com.android.inputmethod.latin.CollectionUtils;
+import android.view.View;
 
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -37,6 +40,7 @@
     }
 
     private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap();
+    private ArrayList<View> mViewCache = CollectionUtils.newArrayList();
 
     public boolean isOpen(final String wordlistId) {
         final State state = mWordlistToState.get(wordlistId);
@@ -64,4 +68,20 @@
             state.mOpen = false;
         }
     }
+
+    public View findFirstOrphanedView() {
+        for (final View v : mViewCache) {
+            if (null == v.getParent()) return v;
+        }
+        return null;
+    }
+
+    public View addToCacheAndReturnView(final View view) {
+        mViewCache.add(view);
+        return view;
+    }
+
+    public void removeFromCache(final View view) {
+        mViewCache.remove(view);
+    }
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
index 6961588..df0e3f0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -28,13 +28,13 @@
      * 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";
+    private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp";
 
     /**
      * Authority for the ContentProvider protocol.
      */
     // TODO: find some way to factorize this string with the one in the resources
-    public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
+    public static final String AUTHORITY = DICTIONARY_DOMAIN;
 
     /**
      * The action of the intent for publishing that new dictionary data is available.
@@ -52,7 +52,14 @@
      */
     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";
+
+    /**
+     * The action of the intent to tell the dictionary provider to update now.
+     */
+    public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN
+            + ".UPDATE_NOW";
 }
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 4fbe162..1d9b999 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -31,6 +31,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -53,7 +54,6 @@
     private static final String QUERY_PARAMETER_MAY_PROMPT_USER = "mayPrompt";
     private static final String QUERY_PARAMETER_TRUE = "true";
     private static final String QUERY_PARAMETER_DELETE_RESULT = "result";
-    private static final String QUERY_PARAMETER_SUCCESS = "success";
     private static final String QUERY_PARAMETER_FAILURE = "failure";
     public static final String QUERY_PARAMETER_PROTOCOL_VERSION = "protocol";
     private static final int NO_MATCH = 0;
@@ -219,7 +219,7 @@
     @Override
     public Cursor query(final Uri uri, final String[] projection, final String selection,
             final String[] selectionArgs, final String sortOrder) {
-        Utils.l("Uri =", uri);
+        DebugLogUtils.l("Uri =", uri);
         PrivateLog.log("Query : " + uri);
         final String clientId = getClientId(uri);
         final int match = matchUri(uri);
@@ -227,7 +227,7 @@
             case DICTIONARY_V1_WHOLE_LIST:
             case DICTIONARY_V2_WHOLE_LIST:
                 final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId);
-                Utils.l("List of dictionaries with count", c.getCount());
+                DebugLogUtils.l("List of dictionaries with count", c.getCount());
                 PrivateLog.log("Returned a list of " + c.getCount() + " items");
                 return c;
             case DICTIONARY_V2_DICT_INFO:
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 46bb554..41916b6 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,14 +22,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
-import android.text.format.DateUtils;
-import android.util.Log;
 import android.widget.Toast;
 
 import com.android.inputmethod.latin.R;
 
 import java.util.Locale;
 import java.util.Random;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Service that handles background tasks for the dictionary provider.
@@ -49,17 +50,10 @@
  *     to access, and mark the current state as such.
  */
 public final class DictionaryService extends Service {
-    private static final String TAG = DictionaryService.class.getName();
-
     /**
      * The package name, to use in the intent actions.
      */
-    private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin";
-
-    /**
-     * The action of the intent to tell the dictionary provider to update now.
-     */
-    private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW";
+    private static final String PACKAGE_NAME = "com.android.inputmethod.latin";
 
     /**
      * The action of the date changing, used to schedule a periodic freshness check
@@ -82,36 +76,42 @@
      * How often, in milliseconds, we want to update the metadata. This is a
      * floor value; actually, it may happen several hours later, or even more.
      */
-    private static final long UPDATE_FREQUENCY = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
 
     /**
      * We are waked around midnight, local time. We want to wake between midnight and 6 am,
      * roughly. So use a random time between 0 and this delay.
      */
-    private static final int MAX_ALARM_DELAY = 6 * ((int)AlarmManager.INTERVAL_HOUR);
+    private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
 
     /**
      * How long we consider a "very long time". If no update took place in this time,
      * the content provider will trigger an update in the background.
      */
-    private static final long VERY_LONG_TIME = 14 * DateUtils.DAY_IN_MILLIS;
+    private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
 
     /**
-     * The last seen start Id. This must be stored because we must only call stopSelfResult() with
-     * the last seen Id, or the service won't stop.
+     * An executor that serializes tasks given to it.
      */
-    private int mLastSeenStartId;
-
-    /**
-     * The command count. We need this because we need to not call stopSelfResult() while we still
-     * have commands running.
-     */
-    private int mCommandCount;
+    private ThreadPoolExecutor mExecutor;
+    private static final int WORKER_THREAD_TIMEOUT_SECONDS = 15;
 
     @Override
     public void onCreate() {
-        mLastSeenStartId = 0;
-        mCommandCount = 0;
+        // By default, a thread pool executor does not timeout its core threads, so it will
+        // never kill them when there isn't any work to do any more. That would mean the service
+        // can never die! By creating it this way and calling allowCoreThreadTimeOut, we allow
+        // the single thread to time out after WORKER_THREAD_TIMEOUT_SECONDS = 15 seconds, allowing
+        // the process to be reclaimed by the system any time after that if it's not doing
+        // anything else.
+        // Executors#newSingleThreadExecutor creates a ThreadPoolExecutor but it returns the
+        // superclass ExecutorService which does not have the #allowCoreThreadTimeOut method,
+        // so we can't use that.
+        mExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */,
+                WORKER_THREAD_TIMEOUT_SECONDS /* keepAliveTime */,
+                TimeUnit.SECONDS /* unit for keepAliveTime */,
+                new LinkedBlockingQueue<Runnable>() /* workQueue */);
+        mExecutor.allowCoreThreadTimeOut(true);
     }
 
     @Override
@@ -136,33 +136,35 @@
      * - Handle a finished download.
      *     This executes the actions that must be taken after a file (metadata or dictionary data
      *     has been downloaded (or failed to download).
+     * The commands that can be spun an another thread will be executed serially, in order, on
+     * a worker thread that is created on demand and terminates after a short while if there isn't
+     * any work left to do.
      */
     @Override
     public synchronized int onStartCommand(final Intent intent, final int flags,
             final int startId) {
         final DictionaryService self = this;
-        mLastSeenStartId = startId;
-        mCommandCount += 1;
         if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
             // This is a UI action, it can't be run in another thread
             showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
                     intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
         } else {
-            // If it's a command that does not require UI, create a thread to do the work
-            // and return right away. DATE_CHANGED or UPDATE_NOW are examples of such commands.
-            new Thread("updateOrFinishDownload") {
+            // If it's a command that does not require UI, arrange for the work to be done on a
+            // separate thread, so that we can return right away. The executor will spawn a thread
+            // if necessary, or reuse a thread that has become idle as appropriate.
+            // DATE_CHANGED or UPDATE_NOW are examples of commands that can be done on another
+            // thread.
+            mExecutor.submit(new Runnable() {
                 @Override
                 public void run() {
                     dispatchBroadcast(self, intent);
-                    synchronized(self) {
-                        if (--mCommandCount <= 0) {
-                            if (!stopSelfResult(mLastSeenStartId)) {
-                                Log.e(TAG, "Can't stop ourselves");
-                            }
-                        }
-                    }
+                    // Since calls to onStartCommand are serialized, the submissions to the executor
+                    // are serialized. That means we are guaranteed to call the stopSelfResult()
+                    // in the same order that we got them, so we don't need to take care of the
+                    // order.
+                    stopSelfResult(startId);
                 }
-            }.start();
+            });
         }
         return Service.START_REDELIVER_INTENT;
     }
@@ -173,9 +175,9 @@
             // at midnight local time, but it may happen if the user changes the date
             // by hand or something similar happens.
             checkTimeAndMaybeSetupUpdateAlarm(context);
-        } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
+        } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
             // Intent to trigger an update now.
-            UpdateHandler.update(context, false);
+            UpdateHandler.tryUpdate(context, false);
         } else {
             UpdateHandler.downloadFinished(context, intent);
         }
@@ -196,7 +198,7 @@
         // It doesn't matter too much if this is very inexact.
         final long now = System.currentTimeMillis();
         final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY);
-        final Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION);
+        final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION);
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                 updateIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 
@@ -226,7 +228,7 @@
      */
     public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
         if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
-        UpdateHandler.update(context, false);
+        UpdateHandler.tryUpdate(context, false);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 6183223..7bbd041 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -30,6 +30,7 @@
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.animation.AnimationUtils;
@@ -104,9 +105,16 @@
 
     @Override
     public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
-        mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
-        mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-        refreshNetworkState();
+        final String metadataUri =
+                MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
+        // We only add the "Refresh" button if we have a non-empty URL to refresh from. If the
+        // URL is empty, of course we can't refresh so it makes no sense to display this.
+        if (!TextUtils.isEmpty(metadataUri)) {
+            mUpdateNowMenu =
+                    menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
+            mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            refreshNetworkState();
+        }
     }
 
     @Override
@@ -222,7 +230,9 @@
                     refreshNetworkState();
 
                     removeAnyDictSettings(prefScreen);
+                    int i = 0;
                     for (Preference preference : prefList) {
+                        preference.setOrder(i++);
                         prefScreen.addPreference(preference);
                     }
                 }
@@ -302,7 +312,7 @@
                 // the description.
                 final String key = matchLevelString + "." + description + "." + wordlistId;
                 final WordListPreference existingPref = prefMap.get(key);
-                if (null == existingPref || hasPriority(status, existingPref.mStatus)) {
+                if (null == existingPref || existingPref.hasPriorityOver(status)) {
                     final WordListPreference oldPreference = mCurrentPreferenceMap.get(key);
                     final WordListPreference pref;
                     if (null != oldPreference
@@ -313,7 +323,7 @@
                         // need to be the same, others have been tested through the key of the
                         // map. Also, status may differ so we don't want to use #equals() here.
                         pref = oldPreference;
-                        pref.mStatus = status;
+                        pref.setStatus(status);
                     } else {
                         // Otherwise, discard it and create a new one instead.
                         pref = new WordListPreference(activity, mDictionaryListInterfaceState,
@@ -329,18 +339,6 @@
         }
     }
 
-    /**
-     * Finds out if a given status has priority over another for display order.
-     *
-     * @param newStatus
-     * @param oldStatus
-     * @return whether newStatus has priority over oldStatus.
-     */
-    private static boolean hasPriority(final int newStatus, final int oldStatus) {
-        // Both of these should be one of MetadataDbHelper.STATUS_*
-        return newStatus > oldStatus;
-    }
-
     @Override
     public boolean onOptionsItemSelected(final MenuItem item) {
         switch (item.getItemId()) {
@@ -363,7 +361,12 @@
         new Thread("updateByHand") {
             @Override
             public void run() {
-                UpdateHandler.update(activity, true);
+                // We call tryUpdate(), which returns whether we could successfully start an update.
+                // If we couldn't, we'll never receive the end callback, so we stop the loading
+                // animation and return to the previous screen.
+                if (!UpdateHandler.tryUpdate(activity, true)) {
+                    stopLoadingAnimation();
+                }
             }
         }.start();
     }
@@ -378,7 +381,9 @@
     private void startLoadingAnimation() {
         mLoadingView.setVisibility(View.VISIBLE);
         getView().setVisibility(View.GONE);
-        mUpdateNowMenu.setTitle(R.string.cancel);
+        // We come here when the menu element is pressed so presumably it can't be null. But
+        // better safe than sorry.
+        if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
     }
 
     private void stopLoadingAnimation() {
diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
index d8aa33b..859f1b3 100644
--- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
@@ -21,8 +21,6 @@
 import android.content.Intent;
 
 public final class EventHandler extends BroadcastReceiver {
-    private static final String TAG = EventHandler.class.getName();
-
     /**
      * Receives a intent broadcast.
      *
diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
index d0e8446..77f67b8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java
@@ -144,7 +144,7 @@
     public static String getMatchLevelSortedString(final int matchLevel) {
         // This works because the match levels are 0~99 (actually 0~30)
         // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
-        return String.format("%02d", MATCH_LEVEL_MAX - matchLevel);
+        return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
index 03ed267..ff5aba6 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -36,8 +37,6 @@
  * Various helper functions for the state database
  */
 public class MetadataDbHelper extends SQLiteOpenHelper {
-
-    @SuppressWarnings("unused")
     private static final String TAG = MetadataDbHelper.class.getSimpleName();
 
     // This was the initial release version of the database. It should never be
@@ -200,6 +199,7 @@
             final ContentValues defaultMetadataValues = new ContentValues();
             defaultMetadataValues.put(CLIENT_CLIENT_ID_COLUMN, "");
             defaultMetadataValues.put(CLIENT_METADATA_URI_COLUMN, defaultMetadataUri);
+            defaultMetadataValues.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
             db.insert(CLIENT_TABLE_NAME, null, defaultMetadataValues);
         }
     }
@@ -359,21 +359,21 @@
     }
 
     /**
-     * Get the metadata download ID for a client ID.
+     * Get the metadata download ID for a metadata URI.
      *
-     * This will retrieve the download ID for the metadata file associated with a client ID.
-     * If there is no metadata download in progress for this client, it will return NOT_AN_ID.
+     * This will retrieve the download ID for the metadata file that has the passed URI.
+     * If this URI is not being downloaded right now, it will return NOT_AN_ID.
      *
      * @param context a context instance to open the database on
-     * @param clientId the client ID to retrieve the metadata download ID of
+     * @param uri the URI to retrieve the metadata download ID of
      * @return the metadata download ID, or NOT_AN_ID if no download is in progress
      */
-    public static long getMetadataDownloadIdForClient(final Context context,
-            final String clientId) {
+    public static long getMetadataDownloadIdForURI(final Context context,
+            final String uri) {
         SQLiteDatabase defaultDb = getDb(context, null);
         final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME,
                 new String[] { CLIENT_PENDINGID_COLUMN },
-                CLIENT_CLIENT_ID_COLUMN + " = ?", new String[] { clientId },
+                CLIENT_METADATA_URI_COLUMN + " = ?", new String[] { uri },
                 null, null, null, null);
         try {
             if (!cursor.moveToFirst()) return UpdateHandler.NOT_AN_ID;
@@ -437,37 +437,37 @@
      */
     public static ContentValues completeWithDefaultValues(final ContentValues result)
             throws BadFormatException {
-        if (!result.containsKey(WORDLISTID_COLUMN) || !result.containsKey(LOCALE_COLUMN)) {
+        if (null == result.get(WORDLISTID_COLUMN) || null == result.get(LOCALE_COLUMN)) {
             throw new BadFormatException();
         }
         // 0 for the pending id, because there is none
-        if (!result.containsKey(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0);
+        if (null == result.get(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0);
         // This is a binary blob of a dictionary
-        if (!result.containsKey(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK);
+        if (null == result.get(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK);
         // This word list is unknown, but it's present, else we wouldn't be here, so INSTALLED
-        if (!result.containsKey(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED);
+        if (null == result.get(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED);
         // No description unless specified, because we can't guess it
-        if (!result.containsKey(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, "");
+        if (null == result.get(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, "");
         // File name - this is an asset, so it works as an already deleted file.
         //     hence, we need to supply a non-existent file name. Anything will
         //     do as long as it returns false when tested with File#exist(), and
         //     the empty string does not, so it's set to "_".
-        if (!result.containsKey(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_");
+        if (null == result.get(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_");
         // No remote file name : this can't be downloaded. Unless specified.
-        if (!result.containsKey(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, "");
+        if (null == result.get(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, "");
         // 0 for the update date : 1970/1/1. Unless specified.
-        if (!result.containsKey(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
+        if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0);
         // Checksum unknown unless specified
-        if (!result.containsKey(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
+        if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, "");
         // No filesize unless specified
-        if (!result.containsKey(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0);
+        if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0);
         // Smallest possible version unless specified
-        if (!result.containsKey(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1);
+        if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1);
         // Assume current format unless specified
-        if (!result.containsKey(FORMATVERSION_COLUMN))
+        if (null == result.get(FORMATVERSION_COLUMN))
             result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION);
         // No flags unless specified
-        if (!result.containsKey(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0);
+        if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0);
         return result;
     }
 
@@ -572,7 +572,8 @@
      * If several clients use the same metadata URL, we know to only download it once, and
      * dispatch the update process across all relevant clients when the download ends. This means
      * several clients may share a single download ID if they share a metadata URI.
-     * The dispatching is done in {@link UpdateHandler#downloadFinished(Context, Intent)}, which
+     * The dispatching is done in
+     * {@link UpdateHandler#downloadFinished(Context, android.content.Intent)}, which
      * finds out about the list of relevant clients by calling this method.
      *
      * @param context a context instance to open the databases
@@ -773,15 +774,17 @@
         if (TextUtils.isEmpty(valuesClientId) || null == valuesMetadataUri
                 || null == valuesMetadataAdditionalId) {
             // We need all these columns to be filled in
-            Utils.l("Missing parameter for updateClientInfo");
+            DebugLogUtils.l("Missing parameter for updateClientInfo");
             return;
         }
         if (!clientId.equals(valuesClientId)) {
             // Mismatch! The client violates the protocol.
-            Utils.l("Received an updateClientInfo request for ", clientId, " but the values "
-                    + "contain a different ID : ", valuesClientId);
+            DebugLogUtils.l("Received an updateClientInfo request for ", clientId,
+                    " but the values " + "contain a different ID : ", valuesClientId);
             return;
         }
+        // Default value for a pending ID is NOT_AN_ID
+        values.put(CLIENT_PENDINGID_COLUMN, UpdateHandler.NOT_AN_ID);
         final SQLiteDatabase defaultDb = getDb(context, "");
         if (-1 == defaultDb.insert(CLIENT_TABLE_NAME, null, values)) {
             defaultDb.update(CLIENT_TABLE_NAME, values,
@@ -848,7 +851,7 @@
             final ContentValues r) {
         switch (r.getAsInteger(TYPE_COLUMN)) {
             case TYPE_BULK:
-                Utils.l("Ended processing a wordlist");
+                DebugLogUtils.l("Ended processing a wordlist");
                 // Updating a bulk word list is a three-step operation:
                 // - Add the new entry to the table
                 // - Remove the old entry from the table
@@ -863,17 +866,20 @@
                                 r.getAsString(WORDLISTID_COLUMN),
                                 Integer.toString(STATUS_INSTALLED) },
                         null, null, null);
-                if (c.moveToFirst()) {
-                    // There should never be more than one file, but if there are, it's a bug
-                    // and we should remove them all. I think it might happen if the power of the
-                    // phone is suddenly cut during an update.
-                    final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
-                    do {
-                        Utils.l("Setting for removal", c.getString(filenameIndex));
-                        filenames.add(c.getString(filenameIndex));
-                    } while (c.moveToNext());
+                try {
+                    if (c.moveToFirst()) {
+                        // There should never be more than one file, but if there are, it's a bug
+                        // and we should remove them all. I think it might happen if the power of
+                        // the phone is suddenly cut during an update.
+                        final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN);
+                        do {
+                            DebugLogUtils.l("Setting for removal", c.getString(filenameIndex));
+                            filenames.add(c.getString(filenameIndex));
+                        } while (c.moveToNext());
+                    }
+                } finally {
+                    c.close();
                 }
-
                 r.put(STATUS_COLUMN, STATUS_INSTALLED);
                 db.beginTransactionNonExclusive();
                 // Delete all old entries. There should never be any stalled entries, but if
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 3f917f1..0e7c3bb 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -38,6 +38,8 @@
 import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -171,25 +173,27 @@
      * Download latest metadata from the server through DownloadManager for all known clients
      * @param context The context for retrieving resources
      * @param updateNow Whether we should update NOW, or respect bandwidth policies
+     * @return true if an update successfully started, false otherwise.
      */
-    public static void update(final Context context, final boolean updateNow) {
+    public static boolean tryUpdate(final Context context, final boolean updateNow) {
         // TODO: loop through all clients instead of only doing the default one.
         final TreeSet<String> uris = new TreeSet<String>();
         final Cursor cursor = MetadataDbHelper.queryClientIds(context);
-        if (null == cursor) return;
+        if (null == cursor) return false;
         try {
-            if (!cursor.moveToFirst()) return;
+            if (!cursor.moveToFirst()) return false;
             do {
                 final String clientId = cursor.getString(0);
                 final String metadataUri =
                         MetadataDbHelper.getMetadataUriAsString(context, clientId);
-                PrivateLog.log("Update for clientId " + Utils.s(clientId));
-                Utils.l("Update for clientId", clientId, " which uses URI ", metadataUri);
+                PrivateLog.log("Update for clientId " + DebugLogUtils.s(clientId));
+                DebugLogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri);
                 uris.add(metadataUri);
             } while (cursor.moveToNext());
         } finally {
             cursor.close();
         }
+        boolean started = false;
         for (final String metadataUri : uris) {
             if (!TextUtils.isEmpty(metadataUri)) {
                 // If the metadata URI is empty, that means we should never update it at all.
@@ -198,8 +202,10 @@
                 // is a bug and it happens anyway, doing nothing is the right thing to do.
                 // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}.
                 updateClientsWithMetadataUri(context, updateNow, metadataUri);
+                started = true;
             }
         }
+        return started;
     }
 
     /**
@@ -211,14 +217,14 @@
      */
     private static void updateClientsWithMetadataUri(final Context context,
             final boolean updateNow, final String metadataUri) {
-        PrivateLog.log("Update for metadata URI " + Utils.s(metadataUri));
+        PrivateLog.log("Update for metadata URI " + DebugLogUtils.s(metadataUri));
         // Adding a disambiguator to circumvent a bug in older versions of DownloadManager.
         // DownloadManager also stupidly cuts the extension to replace with its own that it
         // gets from the content-type. We need to circumvent this.
         final String disambiguator = "#" + System.currentTimeMillis()
-                + com.android.inputmethod.latin.Utils.getVersionName(context) + ".json";
+                + ApplicationUtils.getVersionName(context) + ".json";
         final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator));
-        Utils.l("Request =", metadataRequest);
+        DebugLogUtils.l("Request =", metadataRequest);
 
         final Resources res = context.getResources();
         // By default, download over roaming is allowed and all network types are allowed too.
@@ -254,7 +260,7 @@
         final long downloadId;
         synchronized (sSharedIdProtector) {
             downloadId = manager.enqueue(metadataRequest);
-            Utils.l("Metadata download requested with id", downloadId);
+            DebugLogUtils.l("Metadata download requested with id", downloadId);
             // If there is already a download in progress, it's been there for a while and
             // there is probably something wrong with download manager. It's best to just
             // overwrite the id and request it again. If the old one happens to finish
@@ -266,23 +272,22 @@
     }
 
     /**
-     * Cancels a pending update, if there is one.
+     * Cancels downloading a file, if there is one for this URI.
      *
-     * If none, this is a no-op.
+     * If we are not currently downloading the file at this URI, this is a no-op.
      *
      * @param context the context to open the database on
-     * @param clientId the id of the client
+     * @param metadataUri the URI to cancel
      * @param manager an instance of DownloadManager
      */
     private static void cancelUpdateWithDownloadManager(final Context context,
-            final String clientId, final DownloadManager manager) {
+            final String metadataUri, final DownloadManager manager) {
         synchronized (sSharedIdProtector) {
             final long metadataDownloadId =
-                    MetadataDbHelper.getMetadataDownloadIdForClient(context, clientId);
+                    MetadataDbHelper.getMetadataDownloadIdForURI(context, metadataUri);
             if (NOT_AN_ID == metadataDownloadId) return;
             manager.remove(metadataDownloadId);
-            writeMetadataDownloadId(context,
-                    MetadataDbHelper.getMetadataUriAsString(context, clientId), NOT_AN_ID);
+            writeMetadataDownloadId(context, metadataUri, NOT_AN_ID);
         }
         // Consider a cancellation as a failure. As such, inform listeners that the download
         // has failed.
@@ -292,10 +297,10 @@
     }
 
     /**
-     * Cancels a pending update, if there is one.
+     * Cancels a pending update for this client, if there is one.
      *
-     * If there is none, this is a no-op. This is a helper method that gets the
-     * download manager service.
+     * If we are not currently updating metadata for this client, this is a no-op. This is a helper
+     * method that gets the download manager service and the metadata URI for this client.
      *
      * @param context the context, to get an instance of DownloadManager
      * @param clientId the ID of the client we want to cancel the update of
@@ -303,7 +308,8 @@
     public static void cancelUpdate(final Context context, final String clientId) {
         final DownloadManager manager =
                     (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
-        if (null != manager) cancelUpdateWithDownloadManager(context, clientId, manager);
+        final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId);
+        if (null != manager) cancelUpdateWithDownloadManager(context, metadataUri, manager);
     }
 
     /**
@@ -326,11 +332,11 @@
      */
     public static long registerDownloadRequest(final DownloadManager manager, final Request request,
             final SQLiteDatabase db, final String id, final int version) {
-        Utils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
+        DebugLogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version);
         final long downloadId;
         synchronized (sSharedIdProtector) {
             downloadId = manager.enqueue(request);
-            Utils.l("Download requested with id", downloadId);
+            DebugLogUtils.l("Download requested with id", downloadId);
             MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId);
         }
         return downloadId;
@@ -416,7 +422,7 @@
         // Get and check the ID of the file that was downloaded
         final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID);
         PrivateLog.log("Download finished with id " + fileId);
-        Utils.l("DownloadFinished with id", fileId);
+        DebugLogUtils.l("DownloadFinished with id", fileId);
         if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore
 
         final DownloadManager manager =
@@ -426,7 +432,7 @@
         final ArrayList<DownloadRecord> recordList =
                 getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo);
         if (null == recordList) return; // It was someone else's download.
-        Utils.l("Received result for download ", fileId);
+        DebugLogUtils.l("Received result for download ", fileId);
 
         // TODO: handle gracefully a null pointer here. This is practically impossible because
         // we come here only when DownloadManager explicitly called us when it ended a
@@ -503,7 +509,7 @@
     private static void publishUpdateCycleCompletedEvent(final Context context) {
         // Even if this is not successful, we have to publish the new state.
         PrivateLog.log("Publishing update cycle completed event");
-        Utils.l("Publishing update cycle completed event");
+        DebugLogUtils.l("Publishing update cycle completed event");
         for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) {
             listener.updateCycleCompleted();
         }
@@ -517,12 +523,12 @@
             // {@link handleWordList(Context,InputStream,ContentValues)}.
             // Handle the downloaded file according to its type
             if (downloadRecord.isMetadata()) {
-                Utils.l("Data D/L'd is metadata for", downloadRecord.mClientId);
+                DebugLogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId);
                 // #handleMetadata() closes its InputStream argument
                 handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream(
                         manager.openDownloadedFile(fileId)), downloadRecord.mClientId);
             } else {
-                Utils.l("Data D/L'd is a word list");
+                DebugLogUtils.l("Data D/L'd is a word list");
                 final int wordListStatus = downloadRecord.mAttributes.getAsInteger(
                         MetadataDbHelper.STATUS_COLUMN);
                 if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) {
@@ -582,7 +588,7 @@
      */
     private static void handleMetadata(final Context context, final InputStream stream,
             final String clientId) throws IOException, BadFormatException {
-        Utils.l("Entering handleMetadata");
+        DebugLogUtils.l("Entering handleMetadata");
         final List<WordListMetadata> newMetadata;
         final InputStreamReader reader = new InputStreamReader(stream);
         try {
@@ -592,7 +598,7 @@
             reader.close();
         }
 
-        Utils.l("Downloaded metadata :", newMetadata);
+        DebugLogUtils.l("Downloaded metadata :", newMetadata);
         PrivateLog.log("Downloaded metadata\n" + newMetadata);
 
         final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata);
@@ -617,7 +623,7 @@
         // DownloadManager does not have the ability to put the file directly where we want
         // it, so we had it download to a temporary place. Now we move it. It will be deleted
         // automatically by DownloadManager.
-        Utils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString(
+        DebugLogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString(
                 MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId);
         PrivateLog.log("Downloaded a new word list with description : "
                 + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN)
@@ -676,9 +682,9 @@
      */
     private static void copyFile(final InputStream in, final OutputStream out)
             throws IOException {
-        Utils.l("Copying files");
+        DebugLogUtils.l("Copying files");
         if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) {
-            Utils.l("Not the right types");
+            DebugLogUtils.l("Not the right types");
             copyFileFallback(in, out);
         } else {
             try {
@@ -687,7 +693,7 @@
                 sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel);
             } catch (IOException e) {
                 // Can't work with channels, or something went wrong. Copy by hand.
-                Utils.l("Won't work");
+                DebugLogUtils.l("Won't work");
                 copyFileFallback(in, out);
             }
         }
@@ -702,7 +708,7 @@
      */
     private static void copyFileFallback(final InputStream in, final OutputStream out)
             throws IOException {
-        Utils.l("Falling back to slow copy");
+        DebugLogUtils.l("Falling back to slow copy");
         final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE];
         for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer))
             out.write(buffer, 0, readBytes);
@@ -717,10 +723,10 @@
      */
     private static String getTempFileName(final Context context, final String locale)
             throws IOException {
-        Utils.l("Entering openTempFileOutput");
+        DebugLogUtils.l("Entering openTempFileOutput");
         final File dir = context.getFilesDir();
         final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir);
-        Utils.l("File name is", f.getName());
+        DebugLogUtils.l("File name is", f.getName());
         return f.getName();
     }
 
@@ -741,7 +747,7 @@
             final String clientId, List<WordListMetadata> from, List<WordListMetadata> to) {
         final ActionBatch actions = new ActionBatch();
         // Upgrade existing word lists
-        Utils.l("Comparing dictionaries");
+        DebugLogUtils.l("Comparing dictionaries");
         final Set<String> wordListIds = new TreeSet<String>();
         // TODO: Can these be null?
         if (null == from) from = new ArrayList<WordListMetadata>();
@@ -756,7 +762,7 @@
             final WordListMetadata newInfo = null == metadataInfo
                     || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION
                             ? null : metadataInfo;
-            Utils.l("Considering updating ", id, "currentInfo =", currentInfo);
+            DebugLogUtils.l("Considering updating ", id, "currentInfo =", currentInfo);
 
             if (null == currentInfo && null == newInfo) {
                 // This may happen if a new word list appeared that we can't handle.
@@ -767,7 +773,7 @@
                     // We may come here if there is a new word list that we can't handle.
                     Log.i(TAG, "Can't handle word list with id '" + id + "' because it has format"
                             + " version " + metadataInfo.mFormatVersion + " and the maximum version"
-                            + "we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
+                            + " we can handle is " + MAXIMUM_SUPPORTED_FORMAT_VERSION);
                 }
                 continue;
             } else if (null == currentInfo) {
diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
index 451a0fb..ba1fce1 100644
--- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
+++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java
@@ -61,7 +61,7 @@
     public final Locale mLocale;
     public final String mDescription;
     // The status
-    public int mStatus;
+    private int mStatus;
     // The size of the dictionary file
     private final int mFilesize;
 
@@ -92,12 +92,25 @@
         setKey(wordlistId);
     }
 
-    private void setStatus(final int status) {
+    public void setStatus(final int status) {
         if (status == mStatus) return;
         mStatus = status;
         setSummary(getSummary(status));
     }
 
+    @Override
+    public View onCreateView(final ViewGroup parent) {
+        final View orphanedView = mInterfaceState.findFirstOrphanedView();
+        if (null != orphanedView) return orphanedView; // Will be sent to onBindView
+        final View newView = super.onCreateView(parent);
+        return mInterfaceState.addToCacheAndReturnView(newView);
+    }
+
+    public boolean hasPriorityOver(final int otherPrefStatus) {
+        // Both of these should be one of MetadataDbHelper.STATUS_*
+        return mStatus > otherPrefStatus;
+    }
+
     private String getSummary(final int status) {
         switch (status) {
             // If we are deleting the word list, for the user it's like it's already deleted.
@@ -209,6 +222,9 @@
 
         final ButtonSwitcher buttonSwitcher =
                 (ButtonSwitcher)view.findViewById(R.id.wordlist_button_switcher);
+        // We need to clear the state of the button switcher, because we reuse views; if we didn't
+        // reset it would animate from whatever its old state was.
+        buttonSwitcher.reset(mInterfaceState);
         if (mInterfaceState.isOpen(mWordlistId)) {
             // The button is open.
             final int previousStatus = mInterfaceState.getStatus(mWordlistId);
diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java
index 6efe899..726b920 100644
--- a/java/src/com/android/inputmethod/event/EventInterpreter.java
+++ b/java/src/com/android/inputmethod/event/EventInterpreter.java
@@ -19,9 +19,9 @@
 import android.util.SparseArray;
 import android.view.KeyEvent;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinIME;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 1550e77..09f1145 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -41,7 +41,7 @@
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -113,12 +113,12 @@
     private static final int MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER = 0x80000000;
     private static final int MORE_KEYS_FLAGS_HAS_LABELS = 0x40000000;
     private static final int MORE_KEYS_FLAGS_NEEDS_DIVIDERS = 0x20000000;
-    private static final int MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY = 0x10000000;
+    private static final int MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY = 0x10000000;
     private static final String MORE_KEYS_AUTO_COLUMN_ORDER = "!autoColumnOrder!";
     private static final String MORE_KEYS_FIXED_COLUMN_ORDER = "!fixedColumnOrder!";
     private static final String MORE_KEYS_HAS_LABELS = "!hasLabels!";
     private static final String MORE_KEYS_NEEDS_DIVIDERS = "!needsDividers!";
-    private static final String MORE_KEYS_EMBEDDED_MORE_KEY = "!embeddedMoreKey!";
+    private static final String MORE_KEYS_NO_PANEL_AUTO_MORE_KEY = "!noPanelAutoMoreKey!";
 
     /** Background type that represents different key background visual than normal one. */
     public final int mBackgroundType;
@@ -281,8 +281,8 @@
         if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
             moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
         }
-        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_EMBEDDED_MORE_KEY)) {
-            moreKeysColumn |= MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY;
+        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
+            moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
         }
         mMoreKeysColumnAndFlags = moreKeysColumn;
 
@@ -453,7 +453,7 @@
         } else {
             label = "/" + mLabel;
         }
-        return String.format("%s%s %d,%d %dx%d %s/%s/%s",
+        return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s",
                 Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel,
                 KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType));
     }
@@ -657,8 +657,8 @@
         return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NEEDS_DIVIDERS) != 0;
     }
 
-    public final boolean hasEmbeddedMoreKey() {
-        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_EMBEDDED_MORE_KEY) != 0;
+    public final boolean hasNoPanelAutoMoreKey() {
+        return (mMoreKeysColumnAndFlags & MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY) != 0;
     }
 
     public final String getOutputText() {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index e87ecbc..fefac96 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -21,8 +21,8 @@
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 /**
  * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 9eeee5b..b266986 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -20,16 +20,16 @@
 import com.android.inputmethod.latin.InputPointers;
 
 public interface KeyboardActionListener {
-
     /**
      * Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
      * For keys that repeat, this is only called once.
      *
      * @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
      *            the value will be zero.
+     * @param isRepeatKey true if pressing has occurred while key repeat input.
      * @param isSinglePointer true if pressing has occurred while no other key is being pressed.
      */
-    public void onPressKey(int primaryCode, boolean isSinglePointer);
+    public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer);
 
     /**
      * Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
@@ -99,11 +99,11 @@
      */
     public boolean onCustomRequest(int requestCode);
 
-    public static class Adapter implements KeyboardActionListener {
-        public static final Adapter EMPTY_LISTENER = new Adapter();
+    public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
 
+    public static class Adapter implements KeyboardActionListener {
         @Override
-        public void onPressKey(int primaryCode, boolean isSinglePointer) {}
+        public void onPressKey(int primaryCode, boolean isRepeatKey, boolean isSinglePointer) {}
         @Override
         public void onReleaseKey(int primaryCode, boolean withSliding) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index aa27067..8864b76 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -25,8 +25,8 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
-import com.android.inputmethod.latin.InputTypeUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
@@ -76,7 +76,7 @@
 
     public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
         mSubtype = params.mSubtype;
-        mLocale = SubtypeLocale.getSubtypeLocale(mSubtype);
+        mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
         mOrientation = params.mOrientation;
         mWidth = params.mKeyboardWidth;
         mHeight = params.mKeyboardHeight;
@@ -187,7 +187,7 @@
     public String toString() {
         final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT)
                 ? "port" : "land";
-        return String.format("[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+        return String.format(Locale.ROOT, "[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
                 mLocale,
                 mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1fe23a3..6a900b4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,20 +36,21 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.KeysCache;
-import com.android.inputmethod.latin.AdditionalSubtype;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.InputAttributes;
-import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.SubtypeSwitcher;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -79,6 +80,14 @@
     private final Context mContext;
     private final Params mParams;
 
+    // How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and
+    // ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of
+    // soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts.
+    private static final int FORCIBLE_CACHE_SIZE = 4;
+    // By construction of soft references, anything that is also referenced somewhere else
+    // will stay in the cache. So we forcibly keep some references in an array to prevent
+    // them from disappearing from sKeyboardCache.
+    private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE];
     private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache =
             CollectionUtils.newHashMap();
     private static final KeysCache sKeysCache = new KeysCache();
@@ -109,6 +118,7 @@
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
         InputMethodSubtype mSubtype;
+        boolean mIsSpellChecker;
         int mOrientation;
         int mKeyboardWidth;
         int mKeyboardHeight;
@@ -184,7 +194,18 @@
                     elementParams.mProximityCharsCorrectionEnabled);
             keyboard = builder.build();
             sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
-
+            if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET
+                    || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED)
+                    && !mParams.mIsSpellChecker) {
+                // We only forcibly cache the primary, "ALPHABET", layouts.
+                for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) {
+                    sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1];
+                }
+                sForcibleKeyboardCache[0] = keyboard;
+                if (DEBUG_CACHE) {
+                    Log.d(TAG, "forcing caching of keyboard with id=" + id);
+                }
+            }
             if (DEBUG_CACHE) {
                 Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": "
                         + ((ref == null) ? "LOAD" : "GCed") + " id=" + id);
@@ -267,7 +288,12 @@
                     : subtype;
             mParams.mSubtype = keyboardSubtype;
             mParams.mKeyboardLayoutSetName = KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX
-                    + SubtypeLocale.getKeyboardLayoutSetName(keyboardSubtype);
+                    + SubtypeLocaleUtils.getKeyboardLayoutSetName(keyboardSubtype);
+            return this;
+        }
+
+        public Builder setIsSpellChecker(final boolean isSpellChecker) {
+            mParams.mIsSpellChecker = true;
             return this;
         }
 
@@ -419,11 +445,13 @@
     public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context,
             final String locale, final String layout) {
         final InputMethodSubtype subtype =
-                AdditionalSubtype.createAdditionalSubtype(locale, layout, null);
+                AdditionalSubtypeUtils.createAdditionalSubtype(locale, layout, null);
         return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH,
-                SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
+                SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false /* testCasesHaveTouchCoordinates */,
+                true /* isSpellChecker */);
     }
 
+    @UsedForTesting
     public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
             final InputMethodSubtype subtype, final int orientation,
             final boolean testCasesHaveTouchCoordinates) {
@@ -440,18 +468,20 @@
             throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or "
                     + "ORIENTATION_PORTRAIT: orientation=" + orientation);
         }
-        return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates);
+        return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates,
+                false /* isSpellChecker */);
     }
 
     private static KeyboardLayoutSet createKeyboardSet(final Context context,
             final InputMethodSubtype subtype, final int width, final int height,
-            final boolean testCasesHaveTouchCoordinates) {
+            final boolean testCasesHaveTouchCoordinates, final boolean isSpellChecker) {
         final EditorInfo editorInfo = new EditorInfo();
         editorInfo.inputType = InputType.TYPE_CLASS_TEXT;
         final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(
                 context, editorInfo);
         builder.setScreenGeometry(width, height);
         builder.setSubtype(subtype);
+        builder.setIsSpellChecker(isSpellChecker);
         if (!testCasesHaveTouchCoordinates) {
             // For spell checker and tests
             builder.disableTouchPositionCorrectionData();
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index ad08d64..8880af4 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -29,18 +29,16 @@
 
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
-import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
-import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.InputView;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.SettingsValues;
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsValues;
 
 public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
     private static final String TAG = KeyboardSwitcher.class.getSimpleName();
@@ -68,8 +66,6 @@
         new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich),
     };
 
-    private final AudioAndHapticFeedbackManager mFeedbackManager =
-            AudioAndHapticFeedbackManager.getInstance();
     private SubtypeSwitcher mSubtypeSwitcher;
     private SharedPreferences mPrefs;
 
@@ -151,7 +147,6 @@
         mKeyboardLayoutSet = builder.build();
         try {
             mState.onLoadKeyboard();
-            mFeedbackManager.onSettingsChanged(settingsValues);
         } catch (KeyboardLayoutSetException e) {
             Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause());
             LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause());
@@ -159,10 +154,6 @@
         }
     }
 
-    public void onRingerModeChanged() {
-        mFeedbackManager.onRingerModeChanged();
-    }
-
     public void saveKeyboardState() {
         if (getKeyboard() != null) {
             mState.onSaveKeyboardState();
@@ -217,9 +208,6 @@
     }
 
     public void onPressKey(final int code, final boolean isSinglePointer) {
-        if (isVibrateAndSoundFeedbackRequired()) {
-            mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
-        }
         mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
     }
 
@@ -282,68 +270,27 @@
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
-    public void startDoubleTapTimer() {
+    public void startDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            final TimerProxy timer = keyboardView.getTimerProxy();
-            timer.startDoubleTapTimer();
+            keyboardView.startDoubleTapShiftKeyTimer();
         }
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
-    public void cancelDoubleTapTimer() {
+    public void cancelDoubleTapShiftKeyTimer() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
         if (keyboardView != null) {
-            final TimerProxy timer = keyboardView.getTimerProxy();
-            timer.cancelDoubleTapTimer();
+            keyboardView.cancelDoubleTapShiftKeyTimer();
         }
     }
 
     // Implements {@link KeyboardState.SwitchActions}.
     @Override
-    public boolean isInDoubleTapTimeout() {
+    public boolean isInDoubleTapShiftKeyTimeout() {
         final MainKeyboardView keyboardView = getMainKeyboardView();
-        return (keyboardView != null)
-                ? keyboardView.getTimerProxy().isInDoubleTapTimeout() : false;
-    }
-
-    // Implements {@link KeyboardState.SwitchActions}.
-    @Override
-    public void startLongPressTimer(final int code) {
-        final MainKeyboardView keyboardView = getMainKeyboardView();
-        if (keyboardView != null) {
-            final TimerProxy timer = keyboardView.getTimerProxy();
-            timer.startLongPressTimer(code);
-        }
-    }
-
-    // Implements {@link KeyboardState.SwitchActions}.
-    @Override
-    public void cancelLongPressTimer() {
-        final MainKeyboardView keyboardView = getMainKeyboardView();
-        if (keyboardView != null) {
-            final TimerProxy timer = keyboardView.getTimerProxy();
-            timer.cancelLongPressTimer();
-        }
-    }
-
-    // Implements {@link KeyboardState.SwitchActions}.
-    @Override
-    public void hapticAndAudioFeedback(final int code) {
-        mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView);
-    }
-
-    public void onLongPressTimeout(final int code) {
-        mState.onLongPressTimeout(code);
-    }
-
-    public boolean isInMomentarySwitchState() {
-        return mState.isInMomentarySwitchState();
-    }
-
-    private boolean isVibrateAndSoundFeedbackRequired() {
-        return mKeyboardView != null && !mKeyboardView.isInSlidingKeyInput();
+        return keyboardView != null && keyboardView.isInDoubleTapShiftKeyTimeout();
     }
 
     /**
@@ -367,10 +314,7 @@
                 R.layout.input_view, null);
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
-        if (isHardwareAcceleratedDrawingEnabled) {
-            mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-            // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
-        }
+        mKeyboardView.setHardwareAcceleratedDrawingEnabled(isHardwareAcceleratedDrawingEnabled);
         mKeyboardView.setKeyboardActionListener(mLatinIME);
 
         // This always needs to be set since the accessibility state can
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 7941fcb..054c503 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -32,11 +32,12 @@
 
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyVisualAttributes;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.HashSet;
@@ -153,6 +154,12 @@
                 Color.red(color), Color.green(color), Color.blue(color));
     }
 
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        if (!enabled) return;
+        // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+    }
+
     /**
      * Attaches a keyboard to this view. The keyboard can be switched at any time and the
      * view will re-layout itself to accommodate the keyboard.
@@ -604,4 +611,8 @@
         super.onDetachedFromWindow();
         freeOffscreenBuffer();
     }
+
+    public void deallocateMemory() {
+        freeOffscreenBuffer();
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6c6fc61..6782317 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -21,7 +21,6 @@
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -54,27 +53,24 @@
 import com.android.inputmethod.keyboard.internal.GestureTrailsPreview;
 import com.android.inputmethod.keyboard.internal.KeyDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
+import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
-import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
-import com.android.inputmethod.latin.DebugSettings;
-import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
 import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.DebugSettings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
+import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
-import java.util.Locale;
 import java.util.WeakHashMap;
 
 /**
@@ -119,13 +115,9 @@
  * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration
  */
 public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler,
-        PointerTracker.DrawingProxy, MoreKeysPanel.Controller,
-        TouchScreenRegulator.ProcessMotionEvent {
+        PointerTracker.DrawingProxy, MoreKeysPanel.Controller {
     private static final String TAG = MainKeyboardView.class.getSimpleName();
 
-    // TODO: Kill process when the usability study mode was changed.
-    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
-
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
 
@@ -187,12 +179,8 @@
     // TODO: Make this parameter customizable by user via settings.
     private int mGestureFloatingPreviewTextLingerTimeout;
 
-    private final TouchScreenRegulator mTouchScreenRegulator;
-
     private KeyDetector mKeyDetector;
-    private final boolean mHasDistinctMultitouch;
-    private int mOldPointerCount = 1;
-    private Key mOldKey;
+    private final NonDistinctMultitouchHelper mNonDistinctMultitouchHelper;
 
     private final KeyTimerHandler mKeyTimerHandler;
 
@@ -201,12 +189,9 @@
         private static final int MSG_TYPING_STATE_EXPIRED = 0;
         private static final int MSG_REPEAT_KEY = 1;
         private static final int MSG_LONGPRESS_KEY = 2;
-        private static final int MSG_DOUBLE_TAP = 3;
+        private static final int MSG_DOUBLE_TAP_SHIFT_KEY = 3;
         private static final int MSG_UPDATE_BATCH_INPUT = 4;
 
-        private final int mKeyRepeatStartTimeout;
-        private final int mKeyRepeatInterval;
-        private final int mLongPressShiftLockTimeout;
         private final int mIgnoreAltCodeKeyTimeout;
         private final int mGestureRecognitionUpdateTime;
 
@@ -214,12 +199,6 @@
                 final TypedArray mainKeyboardViewAttr) {
             super(outerInstance);
 
-            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
-            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
-            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
             mIgnoreAltCodeKeyTimeout = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_ignoreAltCodeKeyTimeout, 0);
             mGestureRecognitionUpdateTime = mainKeyboardViewAttr.getInt(
@@ -238,18 +217,10 @@
                 startWhileTypingFadeinAnimation(keyboardView);
                 break;
             case MSG_REPEAT_KEY:
-                final Key currentKey = tracker.getKey();
-                if (currentKey != null && currentKey.mCode == msg.arg1) {
-                    tracker.onRegisterKey(currentKey);
-                    startKeyRepeatTimer(tracker, mKeyRepeatInterval);
-                }
+                tracker.onKeyRepeat(msg.arg1);
                 break;
             case MSG_LONGPRESS_KEY:
-                if (tracker != null) {
-                    keyboardView.onLongPress(tracker);
-                } else {
-                    KeyboardSwitcher.getInstance().onLongPressTimeout(msg.arg1);
-                }
+                keyboardView.onLongPress(tracker);
                 break;
             case MSG_UPDATE_BATCH_INPUT:
                 tracker.updateBatchInputByTimer(SystemClock.uptimeMillis());
@@ -258,19 +229,15 @@
             }
         }
 
-        private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) {
+        @Override
+        public void startKeyRepeatTimer(final PointerTracker tracker, final int delay) {
             final Key key = tracker.getKey();
-            if (key == null) {
+            if (key == null || delay == 0) {
                 return;
             }
             sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay);
         }
 
-        @Override
-        public void startKeyRepeatTimer(final PointerTracker tracker) {
-            startKeyRepeatTimer(tracker, mKeyRepeatStartTimeout);
-        }
-
         public void cancelKeyRepeatTimer() {
             removeMessages(MSG_REPEAT_KEY);
         }
@@ -281,49 +248,10 @@
         }
 
         @Override
-        public void startLongPressTimer(final int code) {
+        public void startLongPressTimer(final PointerTracker tracker, final int delay) {
             cancelLongPressTimer();
-            final int delay;
-            switch (code) {
-            case Constants.CODE_SHIFT:
-                delay = mLongPressShiftLockTimeout;
-                break;
-            default:
-                delay = 0;
-                break;
-            }
-            if (delay > 0) {
-                sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, code, 0), delay);
-            }
-        }
-
-        @Override
-        public void startLongPressTimer(final PointerTracker tracker) {
-            cancelLongPressTimer();
-            if (tracker == null) {
-                return;
-            }
-            final Key key = tracker.getKey();
-            final int delay;
-            switch (key.mCode) {
-            case Constants.CODE_SHIFT:
-                delay = mLongPressShiftLockTimeout;
-                break;
-            default:
-                final int longpressTimeout =
-                        Settings.getInstance().getCurrent().mKeyLongpressTimeout;
-                if (KeyboardSwitcher.getInstance().isInMomentarySwitchState()) {
-                    // We use longer timeout for sliding finger input started from the symbols
-                    // mode key.
-                    delay = longpressTimeout * 3;
-                } else {
-                    delay = longpressTimeout;
-                }
-                break;
-            }
-            if (delay > 0) {
-                sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
-            }
+            if (delay <= 0) return;
+            sendMessageDelayed(obtainMessage(MSG_LONGPRESS_KEY, tracker), delay);
         }
 
         @Override
@@ -390,19 +318,19 @@
         }
 
         @Override
-        public void startDoubleTapTimer() {
-            sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP),
+        public void startDoubleTapShiftKeyTimer() {
+            sendMessageDelayed(obtainMessage(MSG_DOUBLE_TAP_SHIFT_KEY),
                     ViewConfiguration.getDoubleTapTimeout());
         }
 
         @Override
-        public void cancelDoubleTapTimer() {
-            removeMessages(MSG_DOUBLE_TAP);
+        public void cancelDoubleTapShiftKeyTimer() {
+            removeMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
         }
 
         @Override
-        public boolean isInDoubleTapTimeout() {
-            return hasMessages(MSG_DOUBLE_TAP);
+        public boolean isInDoubleTapShiftKeyTimeout() {
+            return hasMessages(MSG_DOUBLE_TAP_SHIFT_KEY);
         }
 
         @Override
@@ -494,19 +422,16 @@
     public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
         super(context, attrs, defStyle);
 
-        mTouchScreenRegulator = new TouchScreenRegulator(context, this);
-
+        PointerTracker.init(getResources());
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         final boolean forceNonDistinctMultitouch = prefs.getBoolean(
                 DebugSettings.PREF_FORCE_NON_DISTINCT_MULTITOUCH, false);
         final boolean hasDistinctMultitouch = context.getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT);
-        mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch;
-        final Resources res = getResources();
-        final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
-                ResourceUtils.getDeviceOverrideValue(
-                        res, R.array.phantom_sudden_move_event_device_list));
-        PointerTracker.init(needsPhantomSuddenMoveEventHack);
+                .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
+                && !forceNonDistinctMultitouch;
+        mNonDistinctMultitouchHelper = hasDistinctMultitouch ? null
+                : new NonDistinctMultitouchHelper();
+
         mPreviewPlacerView = new PreviewPlacerView(context, attrs);
 
         final TypedArray mainKeyboardViewAttr = context.obtainStyledAttributes(
@@ -583,6 +508,14 @@
                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
+
+        mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
+    }
+
+    @Override
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        super.setHardwareAcceleratedDrawingEnabled(enabled);
+        mPreviewPlacerView.setHardwareAcceleratedDrawingEnabled(enabled);
     }
 
     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
@@ -674,7 +607,6 @@
         mKeyDetector.setKeyboard(
                 keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection());
         PointerTracker.setKeyDetector(mKeyDetector);
-        mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth);
         mMoreKeysKeyboardCache.clear();
 
         mSpaceKey = keyboard.getKey(Constants.CODE_SPACE);
@@ -796,8 +728,6 @@
     private static final int STATE_RIGHT = 2;
     private static final int STATE_NORMAL = 0;
     private static final int STATE_HAS_MOREKEYS = 1;
-    private static final int[] KEY_PREVIEW_BACKGROUND_DEFAULT_STATE =
-            KEY_PREVIEW_BACKGROUND_STATE_TABLE[STATE_MIDDLE][STATE_NORMAL];
 
     @Override
     public void showKeyPreview(final PointerTracker tracker) {
@@ -827,10 +757,6 @@
         final KeyDrawParams drawParams = mKeyDrawParams;
         previewText.setTextColor(drawParams.mPreviewTextColor);
         final Drawable background = previewText.getBackground();
-        if (background != null) {
-            background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
-            background.setAlpha(PREVIEW_ALPHA);
-        }
         final String label = key.getPreviewLabel();
         // What we show as preview should match what we show on a key top in onDraw().
         if (label != null) {
@@ -884,6 +810,7 @@
         if (background != null) {
             final int hasMoreKeys = (key.mMoreKeys != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
             background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
+            background.setAlpha(PREVIEW_ALPHA);
         }
         ViewLayoutUtils.placeViewAt(
                 previewText, previewX, previewY, previewWidth, previewHeight);
@@ -927,9 +854,12 @@
     }
 
     @Override
-    public void showGestureTrail(final PointerTracker tracker) {
+    public void showGestureTrail(final PointerTracker tracker,
+            final boolean showsFloatingPreviewText) {
         locatePreviewPlacerView();
-        mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        if (showsFloatingPreviewText) {
+            mGestureFloatingPreviewText.setPreviewPosition(tracker);
+        }
         mGestureTrailsPreview.setPreviewPosition(tracker);
     }
 
@@ -987,57 +917,44 @@
     /**
      * Called when a key is long pressed.
      * @param tracker the pointer tracker which pressed the parent key
-     * @return true if the long press is handled, false otherwise. Subclasses should call the
-     * method on the base class if the subclass doesn't wish to handle the call.
      */
-    private boolean onLongPress(final PointerTracker tracker) {
+    private void onLongPress(final PointerTracker tracker) {
         if (isShowingMoreKeysPanel()) {
-            return false;
+            return;
         }
         final Key key = tracker.getKey();
         if (key == null) {
-            return false;
+            return;
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.mainKeyboardView_onLongPress();
         }
-        final int code = key.mCode;
-        if (key.hasEmbeddedMoreKey()) {
-            final int embeddedCode = key.mMoreKeys[0].mCode;
+        final KeyboardActionListener listener = mKeyboardActionListener;
+        if (key.hasNoPanelAutoMoreKey()) {
+            final int moreKeyCode = key.mMoreKeys[0].mCode;
             tracker.onLongPressed();
-            invokeCodeInput(embeddedCode);
-            invokeReleaseKey(code);
-            KeyboardSwitcher.getInstance().hapticAndAudioFeedback(code);
-            return true;
+            listener.onPressKey(moreKeyCode, false /* isRepeatKey */, true /* isSinglePointer */);
+            listener.onCodeInput(moreKeyCode,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+            listener.onReleaseKey(moreKeyCode, false /* withSliding */);
+            return;
         }
+        final int code = key.mCode;
         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
             // Long pressing the space key invokes IME switcher dialog.
-            if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+            if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
                 tracker.onLongPressed();
-                invokeReleaseKey(code);
-                return true;
+                listener.onReleaseKey(code, false /* withSliding */);
+                return;
             }
         }
-        return openMoreKeysPanel(key, tracker);
+        openMoreKeysPanel(key, tracker);
     }
 
-    private boolean invokeCustomRequest(final int requestCode) {
-        return mKeyboardActionListener.onCustomRequest(requestCode);
-    }
-
-    private void invokeCodeInput(final int code) {
-        mKeyboardActionListener.onCodeInput(
-                code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-    }
-
-    private void invokeReleaseKey(final int code) {
-        mKeyboardActionListener.onReleaseKey(code, false);
-    }
-
-    private boolean openMoreKeysPanel(final Key key, final PointerTracker tracker) {
+    private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
         if (moreKeysPanel == null) {
-            return false;
+            return;
         }
 
         final int[] lastCoords = CoordinateUtils.newInstance();
@@ -1059,7 +976,6 @@
         final int translatedX = moreKeysPanel.translateX(CoordinateUtils.x(lastCoords));
         final int translatedY = moreKeysPanel.translateY(CoordinateUtils.y(lastCoords));
         tracker.onShowMoreKeysPanel(translatedX, translatedY, moreKeysPanel);
-        return true;
     }
 
     public boolean isInSlidingKeyInput() {
@@ -1072,8 +988,9 @@
     @Override
     public void onShowMoreKeysPanel(final MoreKeysPanel panel) {
         locatePreviewPlacerView();
-        if (isShowingMoreKeysPanel()) {
-            onDismissMoreKeysPanel();
+        // TODO: Remove this check
+        if (panel.isShowingInParent()) {
+            panel.dismissMoreKeysPanel();
         }
         mPreviewPlacerView.addView(panel.getContainerView());
         mMoreKeysPanel = panel;
@@ -1085,19 +1002,29 @@
     }
 
     @Override
-    public void onCancelMoreKeysPanel() {
+    public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
         PointerTracker.dismissAllMoreKeysPanels();
     }
 
     @Override
-    public boolean onDismissMoreKeysPanel() {
+    public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
         dimEntireKeyboard(false /* dimmed */);
         if (isShowingMoreKeysPanel()) {
             mPreviewPlacerView.removeView(mMoreKeysPanel.getContainerView());
             mMoreKeysPanel = null;
-            return true;
         }
-        return false;
+    }
+
+    public void startDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.startDoubleTapShiftKeyTimer();
+    }
+
+    public void cancelDoubleTapShiftKeyTimer() {
+        mKeyTimerHandler.cancelDoubleTapShiftKeyTimer();
+    }
+
+    public boolean isInDoubleTapShiftKeyTimeout() {
+        return mKeyTimerHandler.isInDoubleTapShiftKeyTimeout();
     }
 
     @Override
@@ -1113,149 +1040,46 @@
         if (getKeyboard() == null) {
             return false;
         }
-        return mTouchScreenRegulator.onTouchEvent(me);
-    }
-
-    @Override
-    public boolean processMotionEvent(final MotionEvent me) {
-        final boolean nonDistinctMultitouch = !mHasDistinctMultitouch;
-        final int action = me.getActionMasked();
-        final int pointerCount = me.getPointerCount();
-        final int oldPointerCount = mOldPointerCount;
-        mOldPointerCount = pointerCount;
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // If the device does not have distinct multi-touch support panel, ignore all multi-touch
-        // events except a transition from/to single-touch.
-        if (nonDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
-            return true;
-        }
-
-        final long eventTime = me.getEventTime();
-        final int index = me.getActionIndex();
-        final int id = me.getPointerId(index);
-        final int x = (int)me.getX(index);
-        final int y = (int)me.getY(index);
-
-        // TODO: This might be moved to the tracker.processMotionEvent() call below.
-        if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) {
-            writeUsabilityStudyLog(me, action, eventTime, index, id, x, y);
-        }
-        // TODO: This should be moved to the tracker.processMotionEvent() call below.
-        // Currently the same "move" event is being logged twice.
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.mainKeyboardView_processMotionEvent(
-                    me, action, eventTime, index, id, x, y);
-        }
-
-        if (mKeyTimerHandler.isInKeyRepeat()) {
-            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
-            // Key repeating timer will be canceled if 2 or more keys are in action, and current
-            // event (UP or DOWN) is non-modifier key.
-            if (pointerCount > 1 && !tracker.isModifier()) {
+        if (mNonDistinctMultitouchHelper != null) {
+            if (me.getPointerCount() > 1 && mKeyTimerHandler.isInKeyRepeat()) {
+                // Key repeating timer will be canceled if 2 or more keys are in action.
                 mKeyTimerHandler.cancelKeyRepeatTimer();
             }
-            // Up event will pass through.
-        }
-
-        // TODO: cleanup this code into a multi-touch to single-touch event converter class?
-        // Translate mutli-touch event to single-touch events on the device that has no distinct
-        // multi-touch panel.
-        if (nonDistinctMultitouch) {
-            // Use only main (id=0) pointer tracker.
-            final PointerTracker tracker = PointerTracker.getPointerTracker(0, this);
-            if (pointerCount == 1 && oldPointerCount == 2) {
-                // Multi-touch to single touch transition.
-                // Send a down event for the latest pointer if the key is different from the
-                // previous key.
-                final Key newKey = tracker.getKeyOn(x, y);
-                if (mOldKey != newKey) {
-                    tracker.onDownEvent(x, y, eventTime, this);
-                    if (action == MotionEvent.ACTION_UP) {
-                        tracker.onUpEvent(x, y, eventTime);
-                    }
-                }
-            } else if (pointerCount == 2 && oldPointerCount == 1) {
-                // Single-touch to multi-touch transition.
-                // Send an up event for the last pointer.
-                final int[] lastCoords = CoordinateUtils.newInstance();
-                mOldKey = tracker.getKeyOn(
-                        CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords));
-                tracker.onUpEvent(
-                        CoordinateUtils.x(lastCoords), CoordinateUtils.y(lastCoords), eventTime);
-            } else if (pointerCount == 1 && oldPointerCount == 1) {
-                tracker.processMotionEvent(action, x, y, eventTime, this);
-            } else {
-                Log.w(TAG, "Unknown touch panel behavior: pointer count is " + pointerCount
-                        + " (old " + oldPointerCount + ")");
-            }
+            // Non distinct multitouch screen support
+            mNonDistinctMultitouchHelper.processMotionEvent(me, this);
             return true;
         }
+        return processMotionEvent(me);
+    }
 
-        if (action == MotionEvent.ACTION_MOVE) {
-            for (int i = 0; i < pointerCount; i++) {
-                final int pointerId = me.getPointerId(i);
-                final PointerTracker tracker = PointerTracker.getPointerTracker(
-                        pointerId, this);
-                final int px = (int)me.getX(i);
-                final int py = (int)me.getY(i);
-                tracker.onMoveEvent(px, py, eventTime, me);
-                if (ENABLE_USABILITY_STUDY_LOG) {
-                    writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py);
-                }
-                // TODO: This seems to be no longer necessary, and confusing because it leads to
-                // duplicate MotionEvents being recorded.
-                // if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                //     ResearchLogger.mainKeyboardView_processMotionEvent(
-                //             me, action, eventTime, i, pointerId, px, py);
-                // }
-            }
-        } else {
-            final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
-            tracker.processMotionEvent(action, x, y, eventTime, this);
+    public boolean processMotionEvent(final MotionEvent me) {
+        if (LatinImeLogger.sUsabilityStudy) {
+            UsabilityStudyLogUtils.writeMotionEvent(me);
+        }
+        // Currently the same "move" event is being logged twice.
+        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+            ResearchLogger.mainKeyboardView_processMotionEvent(me);
         }
 
+        final int index = me.getActionIndex();
+        final int id = me.getPointerId(index);
+        final PointerTracker tracker = PointerTracker.getPointerTracker(id, this);
+        tracker.processMotionEvent(me, this);
         return true;
     }
 
-    private static void writeUsabilityStudyLog(final MotionEvent me, final int action,
-            final long eventTime, final int index, final int id, final int x, final int y) {
-        final String eventTag;
-        switch (action) {
-        case MotionEvent.ACTION_UP:
-            eventTag = "[Up]";
-            break;
-        case MotionEvent.ACTION_DOWN:
-            eventTag = "[Down]";
-            break;
-        case MotionEvent.ACTION_POINTER_UP:
-            eventTag = "[PointerUp]";
-            break;
-        case MotionEvent.ACTION_POINTER_DOWN:
-            eventTag = "[PointerDown]";
-            break;
-        case MotionEvent.ACTION_MOVE:
-            eventTag = "[Move]";
-            break;
-        default:
-            eventTag = "[Action" + action + "]";
-            break;
-        }
-        final float size = me.getSize(index);
-        final float pressure = me.getPressure(index);
-        UsabilityStudyLogUtils.getInstance().write(
-                eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure);
-    }
-
-    public void cancelAllMessages() {
+    public void cancelAllOngoingEvents() {
         mKeyTimerHandler.cancelAllMessages();
         mDrawingHandler.cancelAllMessages();
+        dismissAllKeyPreviews();
+        dismissGestureFloatingPreviewText();
+        dismissSlidingKeyInputPreview();
+        PointerTracker.dismissAllMoreKeysPanels();
+        PointerTracker.cancelAllPointerTrackers();
     }
 
     public void closing() {
-        dismissAllKeyPreviews();
-        cancelAllMessages();
-        onDismissMoreKeysPanel();
+        cancelAllOngoingEvents();
         mMoreKeysKeyboardCache.clear();
     }
 
@@ -1380,17 +1204,17 @@
     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);
+        final String fullText = SubtypeLocaleUtils.getFullDisplayName(subtype);
         if (fitsTextIntoWidth(width, fullText, paint)) {
             return fullText;
         }
 
-        final String middleText = getMiddleDisplayName(subtype);
+        final String middleText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
         if (fitsTextIntoWidth(width, middleText, paint)) {
             return middleText;
         }
 
-        final String shortText = getShortDisplayName(subtype);
+        final String shortText = SubtypeLocaleUtils.getShortDisplayName(subtype);
         if (fitsTextIntoWidth(width, shortText, paint)) {
             return shortText;
         }
@@ -1437,45 +1261,9 @@
         }
     }
 
-    // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  | Short  Middle      Full
-    // ------ ------- - ---- --------- ----------------------
-    //  en_US qwerty  F  En  English   English (US)           exception
-    //  en_GB qwerty  F  En  English   English (UK)           exception
-    //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Fr  Français  Français
-    //  fr_CA qwerty  F  Fr  Français  Français (Canada)
-    //  de    qwertz  F  De  Deutsch   Deutsch
-    //  zz    qwerty  F      QWERTY    QWERTY
-    //  fr    qwertz  T  Fr  Français  Français
-    //  de    qwerty  T  De  Deutsch   Deutsch
-    //  en_US azerty  T  En  English   English (US)
-    //  zz    azerty  T      AZERTY    AZERTY
-
-    // Get InputMethodSubtype's full display name in its locale.
-    static String getFullDisplayName(final InputMethodSubtype subtype) {
-        if (SubtypeLocale.isNoLanguage(subtype)) {
-            return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
-        }
-        return SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale());
-    }
-
-    // Get InputMethodSubtype's short display name in its locale.
-    static String getShortDisplayName(final InputMethodSubtype subtype) {
-        if (SubtypeLocale.isNoLanguage(subtype)) {
-            return "";
-        }
-        final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
-        return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
-    }
-
-    // Get InputMethodSubtype's middle display name in its locale.
-    static String getMiddleDisplayName(final InputMethodSubtype subtype) {
-        if (SubtypeLocale.isNoLanguage(subtype)) {
-            return SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype);
-        }
-        final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
-        return SubtypeLocale.getSubtypeLocaleDisplayName(locale.getLanguage());
+    @Override
+    public void deallocateMemory() {
+        super.deallocateMemory();
+        mGestureTrailsPreview.deallocateMemory();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index ae08a59..3fd29dc 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.keyboard;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Paint;
 import android.graphics.drawable.Drawable;
 
@@ -28,7 +27,8 @@
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
@@ -75,10 +75,8 @@
                 final boolean isFixedColumnOrder, final int dividerWidth) {
             mIsFixedOrder = isFixedColumnOrder;
             if (parentKeyboardWidth / keyWidth < Math.min(numKeys, maxColumns)) {
-                throw new IllegalArgumentException(
-                        "Keyboard is too small to hold more keys keyboard: "
-                                + parentKeyboardWidth + " " + keyWidth + " "
-                                + numKeys + " " + maxColumns);
+                throw new IllegalArgumentException("Keyboard is too small to hold more keys: "
+                        + parentKeyboardWidth + " " + keyWidth + " " + numKeys + " " + maxColumns);
             }
             mDefaultKeyWidth = keyWidth;
             mDefaultRowHeight = rowHeight;
@@ -279,8 +277,14 @@
             mParentKey = parentKey;
 
             final int width, height;
+            // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
+            // {@link MainKeyboardView#showKeyPreview(PointerTracker}, though there may be
+            // some chances that the value is zero. <code>width == 0</code> will cause
+            // zero-division error at
+            // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
             final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
-                    && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1;
+                    && !parentKey.noKeyPreview() && parentKey.mMoreKeys.length == 1
+                    && keyPreviewDrawParams.mPreviewVisibleWidth > 0;
             if (singleMoreKeyWithPreview) {
                 // Use pre-computed width and height if this more keys keyboard has only one key to
                 // mitigate visual flicker between key preview and more keys keyboard.
@@ -291,22 +295,14 @@
                 // adjusted with their bottom paddings deducted.
                 width = keyPreviewDrawParams.mPreviewVisibleWidth;
                 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,
-                        context.getResources());
+                final float padding = context.getResources().getDimension(
+                        R.dimen.more_keys_keyboard_key_horizontal_padding)
+                        + (parentKey.hasLabelsInMoreKeys()
+                                ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
+                width = getMaxKeyWidth(parentKey, mParams.mDefaultKeyWidth, padding,
+                        parentKeyboardView.newLabelPaint(parentKey));
                 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()) {
@@ -318,16 +314,12 @@
             }
             mParams.setParameters(parentKey.mMoreKeys.length, parentKey.getMoreKeysColumn(),
                     width, height, parentKey.mX + parentKey.mWidth / 2,
-                    parentKeyboardView.getMeasuredWidth(), parentKey.isFixedColumnOrderMoreKeys(),
+                    parentKeyboard.mId.mWidth, parentKey.isFixedColumnOrderMoreKeys(),
                     dividerWidth);
         }
 
-        private static int getMaxKeyWidth(final KeyboardView view, final Key parentKey,
-                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);
+        private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth,
+                final float padding, final Paint paint) {
             int maxWidth = minKeyWidth;
             for (final MoreKeySpec spec : parentKey.mMoreKeys) {
                 final String label = spec.mLabel;
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index a82fb79..94f6a3c 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -23,8 +23,8 @@
 import android.view.View;
 
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 
 /**
  * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and
@@ -34,7 +34,7 @@
     private final int[] mCoordinates = CoordinateUtils.newInstance();
 
     protected final KeyDetector mKeyDetector;
-    private Controller mController;
+    private Controller mController = EMPTY_CONTROLLER;
     protected KeyboardActionListener mListener;
     private int mOriginX;
     private int mOriginY;
@@ -119,7 +119,7 @@
         onMoveKeyInternal(x, y, pointerId);
         if (hasOldKey && mCurrentKey == null) {
             // If the pointer has moved too far away from any target then cancel the panel.
-            mController.onCancelMoreKeysPanel();
+            mController.onCancelMoreKeysPanel(this);
         }
     }
 
@@ -173,9 +173,11 @@
     }
 
     @Override
-    public boolean dismissMoreKeysPanel() {
-        if (mController == null) return false;
-        return mController.onDismissMoreKeysPanel();
+    public void dismissMoreKeysPanel() {
+        if (!isShowingInParent()) {
+            return;
+        }
+        mController.onDismissMoreKeysPanel(this);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
index 9c677e5..886c628 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysPanel.java
@@ -22,21 +22,32 @@
     public interface Controller {
         /**
          * Add the {@link MoreKeysPanel} to the target view.
-         * @param panel
+         * @param panel the panel to be shown.
          */
         public void onShowMoreKeysPanel(final MoreKeysPanel panel);
 
         /**
          * Remove the current {@link MoreKeysPanel} from the target view.
+         * @param panel the panel to be dismissed.
          */
-        public boolean onDismissMoreKeysPanel();
+        public void onDismissMoreKeysPanel(final MoreKeysPanel panel);
 
         /**
          * Instructs the parent to cancel the panel (e.g., when entering a different input mode).
+         * @param panel the panel to be canceled.
          */
-        public void onCancelMoreKeysPanel();
+        public void onCancelMoreKeysPanel(final MoreKeysPanel panel);
     }
 
+    public static final Controller EMPTY_CONTROLLER = new Controller() {
+        @Override
+        public void onShowMoreKeysPanel(final MoreKeysPanel panel) {}
+        @Override
+        public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {}
+        @Override
+        public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {}
+    };
+
     /**
      * Initializes the layout and event handling of this {@link MoreKeysPanel} and calls the
      * controller's onShowMoreKeysPanel to add the panel's container view.
@@ -57,7 +68,7 @@
      * Dismisses the more keys panel and calls the controller's onDismissMoreKeysPanel to remove
      * the panel's container view.
      */
-    public boolean dismissMoreKeysPanel();
+    public void dismissMoreKeysPanel();
 
     /**
      * Process a move event on the more keys panel.
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 1742393..ab5fee9 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -16,8 +16,10 @@
 
 package com.android.inputmethod.keyboard;
 
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.SystemClock;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.MotionEvent;
 
@@ -27,13 +29,15 @@
 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints;
 import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams;
 import com.android.inputmethod.keyboard.internal.PointerTrackerQueue;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.ArrayList;
@@ -84,19 +88,18 @@
         public void dismissKeyPreview(PointerTracker tracker);
         public void showSlidingKeyInputPreview(PointerTracker tracker);
         public void dismissSlidingKeyInputPreview();
-        public void showGestureTrail(PointerTracker tracker);
+        public void showGestureTrail(PointerTracker tracker, boolean showsFloatingPreviewText);
     }
 
     public interface TimerProxy {
         public void startTypingStateTimer(Key typedKey);
         public boolean isTypingState();
-        public void startKeyRepeatTimer(PointerTracker tracker);
-        public void startLongPressTimer(PointerTracker tracker);
-        public void startLongPressTimer(int code);
+        public void startKeyRepeatTimer(PointerTracker tracker, int delay);
+        public void startLongPressTimer(PointerTracker tracker, int delay);
         public void cancelLongPressTimer();
-        public void startDoubleTapTimer();
-        public void cancelDoubleTapTimer();
-        public boolean isInDoubleTapTimeout();
+        public void startDoubleTapShiftKeyTimer();
+        public void cancelDoubleTapShiftKeyTimer();
+        public boolean isInDoubleTapShiftKeyTimeout();
         public void cancelKeyTimers();
         public void startUpdateBatchInputTimer(PointerTracker tracker);
         public void cancelUpdateBatchInputTimer(PointerTracker tracker);
@@ -108,19 +111,17 @@
             @Override
             public boolean isTypingState() { return false; }
             @Override
-            public void startKeyRepeatTimer(PointerTracker tracker) {}
+            public void startKeyRepeatTimer(PointerTracker tracker, int delay) {}
             @Override
-            public void startLongPressTimer(PointerTracker tracker) {}
-            @Override
-            public void startLongPressTimer(int code) {}
+            public void startLongPressTimer(PointerTracker tracker, int delay) {}
             @Override
             public void cancelLongPressTimer() {}
             @Override
-            public void startDoubleTapTimer() {}
+            public void startDoubleTapShiftKeyTimer() {}
             @Override
-            public void cancelDoubleTapTimer() {}
+            public void cancelDoubleTapShiftKeyTimer() {}
             @Override
-            public boolean isInDoubleTapTimeout() { return false; }
+            public boolean isInDoubleTapShiftKeyTimeout() { return false; }
             @Override
             public void cancelKeyTimers() {}
             @Override
@@ -137,6 +138,9 @@
         public final int mTouchNoiseThresholdTime;
         public final int mTouchNoiseThresholdDistance;
         public final int mSuppressKeyPreviewAfterBatchInputDuration;
+        public final int mKeyRepeatStartTimeout;
+        public final int mKeyRepeatInterval;
+        public final int mLongPressShiftLockTimeout;
 
         public static final PointerTrackerParams DEFAULT = new PointerTrackerParams();
 
@@ -145,6 +149,9 @@
             mTouchNoiseThresholdTime = 0;
             mTouchNoiseThresholdDistance = 0;
             mSuppressKeyPreviewAfterBatchInputDuration = 0;
+            mKeyRepeatStartTimeout = 0;
+            mKeyRepeatInterval = 0;
+            mLongPressShiftLockTimeout = 0;
         }
 
         public PointerTrackerParams(final TypedArray mainKeyboardViewAttr) {
@@ -156,6 +163,12 @@
                     R.styleable.MainKeyboardView_touchNoiseThresholdDistance, 0);
             mSuppressKeyPreviewAfterBatchInputDuration = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration, 0);
+            mKeyRepeatStartTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatStartTimeout, 0);
+            mKeyRepeatInterval = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_keyRepeatInterval, 0);
+            mLongPressShiftLockTimeout = mainKeyboardViewAttr.getInt(
+                    R.styleable.MainKeyboardView_longPressShiftLockTimeout, 0);
         }
     }
 
@@ -167,8 +180,9 @@
     // Move this threshold to resource.
     // TODO: Device specific parameter would be better for device specific hack?
     private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth
-    // This hack might be device specific.
-    private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true;
+    // This hack is applied to certain classes of tablets.
+    // See {@link #needsProximateBogusDownMoveUpEventHack(Resources)}.
+    private static boolean sNeedsProximateBogusDownMoveUpEventHack;
 
     private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList();
     private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue();
@@ -178,7 +192,7 @@
     private DrawingProxy mDrawingProxy;
     private TimerProxy mTimerProxy;
     private KeyDetector mKeyDetector;
-    private KeyboardActionListener mListener = KeyboardActionListener.Adapter.EMPTY_LISTENER;
+    private KeyboardActionListener mListener = KeyboardActionListener.EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
     private int mPhantonSuddenMoveThreshold;
@@ -326,6 +340,7 @@
     // the more keys panel currently being shown. equals null if no panel is active.
     private MoreKeysPanel mMoreKeysPanel;
 
+    private static final int MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT = 3;
     // true if this pointer is in a sliding key input.
     boolean mIsInSlidingKeyInput;
     // true if this pointer is in a sliding key input from a modifier key,
@@ -337,8 +352,34 @@
 
     private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints;
 
-    public static void init(final boolean needsPhantomSuddenMoveEventHack) {
-        sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack;
+    private static final int SMALL_TABLET_SMALLEST_WIDTH = 600; // dp
+    private static final int LARGE_TABLET_SMALLEST_WIDTH = 768; // dp
+
+    private static boolean needsProximateBogusDownMoveUpEventHack(final Resources res) {
+        // The proximate bogus down move up event hack is needed for a device such like,
+        // 1) is large tablet, or 2) is small tablet and the screen density is less than hdpi.
+        // Though it seems odd to use screen density as criteria of the quality of the touch
+        // screen, the small table that has a less density screen than hdpi most likely has been
+        // made with the touch screen that needs the hack.
+        final int sw = res.getConfiguration().smallestScreenWidthDp;
+        final boolean isLargeTablet = (sw >= LARGE_TABLET_SMALLEST_WIDTH);
+        final boolean isSmallTablet =
+                (sw >= SMALL_TABLET_SMALLEST_WIDTH && sw < LARGE_TABLET_SMALLEST_WIDTH);
+        final int densityDpi = res.getDisplayMetrics().densityDpi;
+        final boolean hasLowDensityScreen = (densityDpi < DisplayMetrics.DENSITY_HIGH);
+        final boolean needsTheHack = isLargeTablet || (isSmallTablet && hasLowDensityScreen);
+        if (DEBUG_MODE) {
+            Log.d(TAG, "needsProximateBogusDownMoveUpEventHack=" + needsTheHack
+                    + " smallestScreenWidthDp=" + sw + " densityDpi=" + densityDpi);
+        }
+        return needsTheHack;
+    }
+
+    public static void init(final Resources res) {
+        sNeedsPhantomSuddenMoveEventHack = Boolean.parseBoolean(
+                ResourceUtils.getDeviceOverrideValue(
+                        res, R.array.phantom_sudden_move_event_device_list));
+        sNeedsProximateBogusDownMoveUpEventHack = needsProximateBogusDownMoveUpEventHack(res);
         sParams = PointerTrackerParams.DEFAULT;
         sGestureStrokeParams = GestureStrokeParams.DEFAULT;
         sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT;
@@ -386,6 +427,10 @@
         return sPointerTrackerQueue.isAnyInSlidingKeyInput();
     }
 
+    public static void cancelAllPointerTrackers() {
+        sPointerTrackerQueue.cancelAllPointerTrackers();
+    }
+
     public static void setKeyboardActionListener(final KeyboardActionListener listener) {
         final int trackersSize = sTrackers.size();
         for (int i = 0; i < trackersSize; ++i) {
@@ -433,6 +478,10 @@
         mPointerId = id;
         mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints(
                 id, sGestureStrokeParams, sGesturePreviewParams);
+        setKeyEventHandler(handler);
+    }
+
+    private void setKeyEventHandler(final KeyEventHandler handler) {
         setKeyDetectorInner(handler.getKeyDetector());
         mListener = handler.getKeyboardActionListener();
         mDrawingProxy = handler.getDrawingProxy();
@@ -440,7 +489,8 @@
     }
 
     // Returns true if keyboard has been changed by this callback.
-    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key) {
+    private boolean callListenerOnPressAndCheckKeyboardLayoutChange(final Key key,
+            final boolean isRepeatKey) {
         // While gesture input is going on, this method should be a no-operation. But when gesture
         // input has been canceled, <code>sInGesture</code> and <code>mIsDetectingGesture</code>
         // are set to false. To keep this method is a no-operation,
@@ -450,16 +500,17 @@
         }
         final boolean ignoreModifierKey = mIsInSlidingKeyInput && key.isModifier();
         if (DEBUG_LISTENER) {
-            Log.d(TAG, String.format("[%d] onPress    : %s%s%s", mPointerId,
+            Log.d(TAG, String.format("[%d] onPress    : %s%s%s%s", mPointerId,
                     KeyDetector.printableCode(key),
                     ignoreModifierKey ? " ignoreModifier" : "",
-                    key.isEnabled() ? "" : " disabled"));
+                    key.isEnabled() ? "" : " disabled",
+                    isRepeatKey ? " repeat" : ""));
         }
         if (ignoreModifierKey) {
             return false;
         }
         if (key.isEnabled()) {
-            mListener.onPressKey(key.mCode, getActivePointerTrackerCount() == 1);
+            mListener.onPressKey(key.mCode, isRepeatKey, getActivePointerTrackerCount() == 1);
             final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
             mKeyboardLayoutHasBeenChanged = false;
             mTimerProxy.startTypingStateTimer(key);
@@ -570,10 +621,6 @@
         return mIsInSlidingKeyInput;
     }
 
-    public boolean isInSlidingKeyInputFromModifier() {
-        return mIsInSlidingKeyInputFromModifier;
-    }
-
     public Key getKey() {
         return mCurrentKey;
     }
@@ -721,7 +768,7 @@
         return sPointerTrackerQueue.size();
     }
 
-    public boolean isOldestTrackerInQueue() {
+    private boolean isOldestTrackerInQueue() {
         return sPointerTrackerQueue.getOldestElement() == this;
     }
 
@@ -744,7 +791,9 @@
             dismissAllMoreKeysPanels();
         }
         mTimerProxy.cancelLongPressTimer();
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     public void updateBatchInputByTimer(final long eventTime) {
@@ -760,7 +809,9 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void updateBatchInput(final long eventTime) {
@@ -801,11 +852,13 @@
         if (mIsTrackingForActionDisabled) {
             return;
         }
-        mDrawingProxy.showGestureTrail(this);
+        // A gesture floating preview text will be shown at the oldest pointer/finger on the screen.
+        mDrawingProxy.showGestureTrail(
+                this, isOldestTrackerInQueue() /* showsFloatingPreviewText */);
     }
 
     private void cancelBatchInput() {
-        sPointerTrackerQueue.cancelAllPointerTracker();
+        cancelAllPointerTrackers();
         mIsDetectingGesture = false;
         if (!sInGesture) {
             return;
@@ -817,8 +870,23 @@
         mListener.onCancelBatchInput();
     }
 
-    public void processMotionEvent(final int action, final int x, final int y, final long eventTime,
-            final KeyEventHandler handler) {
+    public void processMotionEvent(final MotionEvent me, final KeyEventHandler handler) {
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        if (action == MotionEvent.ACTION_MOVE) {
+            final int pointerCount = me.getPointerCount();
+            for (int index = 0; index < pointerCount; index++) {
+                final int id = me.getPointerId(index);
+                final PointerTracker tracker = getPointerTracker(id, handler);
+                final int x = (int)me.getX(index);
+                final int y = (int)me.getY(index);
+                tracker.onMoveEvent(x, y, eventTime, me);
+            }
+            return;
+        }
+        final int index = me.getActionIndex();
+        final int x = (int)me.getX(index);
+        final int y = (int)me.getY(index);
         switch (action) {
         case MotionEvent.ACTION_DOWN:
         case MotionEvent.ACTION_POINTER_DOWN:
@@ -828,24 +896,18 @@
         case MotionEvent.ACTION_POINTER_UP:
             onUpEvent(x, y, eventTime);
             break;
-        case MotionEvent.ACTION_MOVE:
-            onMoveEvent(x, y, eventTime, null);
-            break;
         case MotionEvent.ACTION_CANCEL:
             onCancelEvent(x, y, eventTime);
             break;
         }
     }
 
-    public void onDownEvent(final int x, final int y, final long eventTime,
+    private void onDownEvent(final int x, final int y, final long eventTime,
             final KeyEventHandler handler) {
         if (DEBUG_EVENT) {
             printTouchEvent("onDownEvent:", x, y, eventTime);
         }
-        mDrawingProxy = handler.getDrawingProxy();
-        mTimerProxy = handler.getTimerProxy();
-        setKeyboardActionListener(handler.getKeyboardActionListener());
-        setKeyDetectorInner(handler.getKeyDetector());
+        setKeyEventHandler(handler);
         // Naive up-to-down noise filter.
         final long deltaT = eventTime - mUpTime;
         if (deltaT < sParams.mTouchNoiseThresholdTime) {
@@ -877,7 +939,7 @@
         }
         // A gesture should start only from a non-modifier key.
         mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard()
-                && key != null && !key.isModifier();
+                && key != null && !key.isModifier() && !key.isRepeatable();
         if (mIsDetectingGesture) {
             if (getActivePointerTrackerCount() == 1) {
                 sGestureFirstDownTime = eventTime;
@@ -905,7 +967,7 @@
             // This onPress call may have changed keyboard layout. Those cases are detected at
             // {@link #setKeyboard}. In those cases, we should update key according to the new
             // keyboard layout.
-            if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+            if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
                 key = onDownKey(x, y, eventTime);
             }
 
@@ -955,7 +1017,7 @@
         }
     }
 
-    public void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
+    private void onMoveEvent(final int x, final int y, final long eventTime, final MotionEvent me) {
         if (DEBUG_MOVE_EVENT) {
             printTouchEvent("onMoveEvent:", x, y, eventTime);
         }
@@ -981,7 +1043,9 @@
             final int translatedY = mMoreKeysPanel.translateY(y);
             mMoreKeysPanel.onMoveEvent(translatedX, translatedY, mPointerId, eventTime);
             onMoveKey(x, y);
-            mDrawingProxy.showSlidingKeyInputPreview(this);
+            if (mIsInSlidingKeyInputFromModifier) {
+                mDrawingProxy.showSlidingKeyInputPreview(this);
+            }
             return;
         }
         onMoveEventInternal(x, y, eventTime);
@@ -993,7 +1057,7 @@
         // at {@link #setKeyboard}. In those cases, we should update key according
         // to the new keyboard layout.
         Key key = newKey;
-        if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) {
+        if (callListenerOnPressAndCheckKeyboardLayoutChange(key, false /* isRepeatKey */)) {
             key = onMoveKey(x, y);
         }
         onMoveToNewKey(key, x, y);
@@ -1136,10 +1200,12 @@
                 slideOutFromOldKey(oldKey, x, y);
             }
         }
-        mDrawingProxy.showSlidingKeyInputPreview(this);
+        if (mIsInSlidingKeyInputFromModifier) {
+            mDrawingProxy.showSlidingKeyInputPreview(this);
+        }
     }
 
-    public void onUpEvent(final int x, final int y, final long eventTime) {
+    private void onUpEvent(final int x, final int y, final long eventTime) {
         if (DEBUG_EVENT) {
             printTouchEvent("onUpEvent  :", x, y, eventTime);
         }
@@ -1239,13 +1305,13 @@
         sPointerTrackerQueue.remove(this);
     }
 
-    public void onCancelEvent(final int x, final int y, final long eventTime) {
+    private void onCancelEvent(final int x, final int y, final long eventTime) {
         if (DEBUG_EVENT) {
             printTouchEvent("onCancelEvt:", x, y, eventTime);
         }
 
         cancelBatchInput();
-        sPointerTrackerQueue.cancelAllPointerTracker();
+        cancelAllPointerTrackers();
         sPointerTrackerQueue.releaseAllPointers(eventTime);
         onCancelEventInternal();
     }
@@ -1260,23 +1326,6 @@
         }
     }
 
-    private void startRepeatKey(final Key key) {
-        if (sInGesture) return;
-        if (key == null) return;
-        if (!key.isRepeatable()) return;
-        // Don't start key repeat when we are in sliding input mode.
-        if (mIsInSlidingKeyInput) return;
-        onRegisterKey(key);
-        mTimerProxy.startKeyRepeatTimer(this);
-    }
-
-    public void onRegisterKey(final Key key) {
-        if (key != null) {
-            detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-            mTimerProxy.startTypingStateTimer(key);
-        }
-    }
-
     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
             final Key newKey) {
         if (mKeyDetector == null) {
@@ -1328,7 +1377,22 @@
         // We always need to start the long press timer if the key has its more keys regardless of
         // whether or not we are in the sliding input mode.
         if (mIsInSlidingKeyInput && key.mMoreKeys == null) return;
-        mTimerProxy.startLongPressTimer(this);
+        final int delay;
+        switch (key.mCode) {
+        case Constants.CODE_SHIFT:
+            delay = sParams.mLongPressShiftLockTimeout;
+            break;
+        default:
+            final int longpressTimeout = Settings.getInstance().getCurrent().mKeyLongpressTimeout;
+            if (mIsInSlidingKeyInputFromModifier) {
+                // We use longer timeout for sliding finger input started from the modifier key.
+                delay = longpressTimeout * MULTIPLIER_FOR_LONG_PRESS_TIMEOUT_IN_SLIDING_INPUT;
+            } else {
+                delay = longpressTimeout;
+            }
+            break;
+        }
+        mTimerProxy.startLongPressTimer(this, delay);
     }
 
     private void detectAndSendKey(final Key key, final int x, final int y, final long eventTime) {
@@ -1342,6 +1406,26 @@
         callListenerOnRelease(key, code, false /* withSliding */);
     }
 
+    private void startRepeatKey(final Key key) {
+        if (sInGesture) return;
+        if (key == null) return;
+        if (!key.isRepeatable()) return;
+        // Don't start key repeat when we are in sliding input mode.
+        if (mIsInSlidingKeyInput) return;
+        detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatStartTimeout);
+    }
+
+    public void onKeyRepeat(final int code) {
+        final Key key = getKey();
+        if (key == null || key.mCode != code) {
+            return;
+        }
+        mTimerProxy.startKeyRepeatTimer(this, sParams.mKeyRepeatInterval);
+        callListenerOnPressAndCheckKeyboardLayoutChange(key, true /* isRepeatKey */);
+        callListenerOnCodeInput(key, code, mKeyX, mKeyY, SystemClock.uptimeMillis());
+    }
+
     private void printTouchEvent(final String title, final int x, final int y,
             final long eventTime) {
         final Key key = mKeyDetector.detectHitKey(x, y);
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 57d3fed..9b0a33c 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -22,7 +22,7 @@
 
 import com.android.inputmethod.keyboard.internal.TouchPositionCorrection;
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.JniUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
 
 import java.util.Arrays;
 
@@ -240,28 +240,121 @@
 
     private void computeNearestNeighbors() {
         final int defaultWidth = mMostCommonKeyWidth;
-        final Key[] keys = mKeys;
-        final int thresholdBase = (int) (defaultWidth * SEARCH_DISTANCE);
-        final int threshold = thresholdBase * thresholdBase;
+        final int keyCount = mKeys.length;
+        final int gridSize = mGridNeighbors.length;
+        final int threshold = (int) (defaultWidth * SEARCH_DISTANCE);
+        final int thresholdSquared = threshold * threshold;
         // Round-up so we don't have any pixels outside the grid
-        final Key[] neighborKeys = new Key[keys.length];
-        final int gridWidth = mGridWidth * mCellWidth;
-        final int gridHeight = mGridHeight * mCellHeight;
-        for (int x = 0; x < gridWidth; x += mCellWidth) {
-            for (int y = 0; y < gridHeight; y += mCellHeight) {
-                final int centerX = x + mCellWidth / 2;
-                final int centerY = y + mCellHeight / 2;
-                int count = 0;
-                for (final Key key : keys) {
-                    if (key.isSpacer()) continue;
-                    if (key.squaredDistanceToEdge(centerX, centerY) < threshold) {
-                        neighborKeys[count++] = key;
+        final int fullGridWidth = mGridWidth * mCellWidth;
+        final int fullGridHeight = mGridHeight * mCellHeight;
+
+        // For large layouts, 'neighborsFlatBuffer' is about 80k of memory: gridSize is usually 512,
+        // keycount is about 40 and a pointer to a Key is 4 bytes. This contains, for each cell,
+        // enough space for as many keys as there are on the keyboard. Hence, every
+        // keycount'th element is the start of a new cell, and each of these virtual subarrays
+        // start empty with keycount spaces available. This fills up gradually in the loop below.
+        // Since in the practice each cell does not have a lot of neighbors, most of this space is
+        // actually just empty padding in this fixed-size buffer.
+        final Key[] neighborsFlatBuffer = new Key[gridSize * keyCount];
+        final int[] neighborCountPerCell = new int[gridSize];
+        final int halfCellWidth = mCellWidth / 2;
+        final int halfCellHeight = mCellHeight / 2;
+        for (final Key key : mKeys) {
+            if (key.isSpacer()) continue;
+
+/* HOW WE PRE-SELECT THE CELLS (iterate over only the relevant cells, instead of all of them)
+
+  We want to compute the distance for keys that are in the cells that are close enough to the
+  key border, as this method is performance-critical. These keys are represented with 'star'
+  background on the diagram below. Let's consider the Y case first.
+
+  We want to select the cells which center falls between the top of the key minus the threshold,
+  and the bottom of the key plus the threshold.
+  topPixelWithinThreshold is key.mY - threshold, and bottomPixelWithinThreshold is
+  key.mY + key.mHeight + threshold.
+
+  Then we need to compute the center of the top row that we need to evaluate, as we'll iterate
+  from there.
+
+(0,0)----> x
+| .-------------------------------------------.
+| |   |   |   |   |   |   |   |   |   |   |   |
+| |---+---+---+---+---+---+---+---+---+---+---|   .- top of top cell (aligned on the grid)
+| |   |   |   |   |   |   |   |   |   |   |   |   |
+| |-----------+---+---+---+---+---+---+---+---|---'                          v
+| |   |   |   |***|***|*_________________________ topPixelWithinThreshold    | yDeltaToGrid
+| |---+---+---+-----^-+-|-+---+---+---+---+---|                              ^
+| |   |   |   |***|*|*|*|*|***|***|   |   |   |           ______________________________________
+v |---+---+--threshold--|-+---+---+---+---+---|          |
+  |   |   |   |***|*|*|*|*|***|***|   |   |   |          | Starting from key.mY, we substract
+y |---+---+---+---+-v-+-|-+---+---+---+---+---|          | thresholdBase and get the top pixel
+  |   |   |   |***|**########------------------- key.mY  | within the threshold. We align that on
+  |---+---+---+---+--#+---+-#-+---+---+---+---|          | the grid by computing the delta to the
+  |   |   |   |***|**#|***|*#*|***|   |   |   |          | grid, and get the top of the top cell.
+  |---+---+---+---+--#+---+-#-+---+---+---+---|          |
+  |   |   |   |***|**########*|***|   |   |   |          | Adding half the cell height to the top
+  |---+---+---+---+---+-|-+---+---+---+---+---|          | of the top cell, we get the middle of
+  |   |   |   |***|***|*|*|***|***|   |   |   |          | the top cell (yMiddleOfTopCell).
+  |---+---+---+---+---+-|-+---+---+---+---+---|          |
+  |   |   |   |***|***|*|*|***|***|   |   |   |          |
+  |---+---+---+---+---+-|________________________ yEnd   | Since we only want to add the key to
+  |   |   |   |   |   |   | (bottomPixelWithinThreshold) | the proximity if it's close enough to
+  |---+---+---+---+---+---+---+---+---+---+---|          | the center of the cell, we only need
+  |   |   |   |   |   |   |   |   |   |   |   |          | to compute for these cells where
+  '---'---'---'---'---'---'---'---'---'---'---'          | topPixelWithinThreshold is above the
+                                        (positive x,y)   | center of the cell. This is the case
+                                                         | when yDeltaToGrid is less than half
+  [Zoomed in diagram]                                    | the height of the cell.
+  +-------+-------+-------+-------+-------+              |
+  |       |       |       |       |       |              | On the zoomed in diagram, on the right
+  |       |       |       |       |       |              | the topPixelWithinThreshold (represented
+  |       |       |       |       |       |      top of  | with an = sign) is below and we can skip
+  +-------+-------+-------+--v----+-------+ .. top cell  | this cell, while on the left it's above
+  |       | = topPixelWT  |  |  yDeltaToGrid             | and we need to compute for this cell.
+  |..yStart.|.....|.......|..|....|.......|... y middle  | Thus, if yDeltaToGrid is more than half
+  |   (left)|     |       |  ^ =  |       | of top cell  | the height of the cell, we start the
+  +-------+-|-----+-------+----|--+-------+              | iteration one cell below the top cell,
+  |       | |     |       |    |  |       |              | else we start it on the top cell. This
+  |.......|.|.....|.......|....|..|.....yStart (right)   | is stored in yStart.
+
+  Since we only want to go up to bottomPixelWithinThreshold, and we only iterate on the center
+  of the keys, we can stop as soon as the y value exceeds bottomPixelThreshold, so we don't
+  have to align this on the center of the key. Hence, we don't need a separate value for
+  bottomPixelWithinThreshold and call this yEnd right away.
+*/
+            final int topPixelWithinThreshold = key.mY - threshold;
+            final int yDeltaToGrid = topPixelWithinThreshold % mCellHeight;
+            final int yMiddleOfTopCell = topPixelWithinThreshold - yDeltaToGrid + halfCellHeight;
+            final int yStart = Math.max(halfCellHeight,
+                    yMiddleOfTopCell + (yDeltaToGrid <= halfCellHeight ? 0 : mCellHeight));
+            final int yEnd = Math.min(fullGridHeight, key.mY + key.mHeight + threshold);
+
+            final int leftPixelWithinThreshold = key.mX - threshold;
+            final int xDeltaToGrid = leftPixelWithinThreshold % mCellWidth;
+            final int xMiddleOfLeftCell = leftPixelWithinThreshold - xDeltaToGrid + halfCellWidth;
+            final int xStart = Math.max(halfCellWidth,
+                    xMiddleOfLeftCell + (xDeltaToGrid <= halfCellWidth ? 0 : mCellWidth));
+            final int xEnd = Math.min(fullGridWidth, key.mX + key.mWidth + threshold);
+
+            int baseIndexOfCurrentRow = (yStart / mCellHeight) * mGridWidth + (xStart / mCellWidth);
+            for (int centerY = yStart; centerY <= yEnd; centerY += mCellHeight) {
+                int index = baseIndexOfCurrentRow;
+                for (int centerX = xStart; centerX <= xEnd; centerX += mCellWidth) {
+                    if (key.squaredDistanceToEdge(centerX, centerY) < thresholdSquared) {
+                        neighborsFlatBuffer[index * keyCount + neighborCountPerCell[index]] = key;
+                        ++neighborCountPerCell[index];
                     }
+                    ++index;
                 }
-                mGridNeighbors[(y / mCellHeight) * mGridWidth + (x / mCellWidth)] =
-                        Arrays.copyOfRange(neighborKeys, 0, count);
+                baseIndexOfCurrentRow += mGridWidth;
             }
         }
+
+        for (int i = 0; i < gridSize; ++i) {
+            final int base = i * keyCount;
+            mGridNeighbors[i] =
+                    Arrays.copyOfRange(neighborsFlatBuffer, base, base + neighborCountPerCell[i]);
+        }
     }
 
     public void fillArrayWithNearestKeyCodes(final int x, final int y, final int primaryKeyCode,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
index ab810a5..c6dd9e1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java
@@ -26,9 +26,9 @@
 import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 
 /**
  * The class for single gesture preview text. The class for multiple gesture preview text will be
@@ -115,9 +115,7 @@
 
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        final boolean needsToUpdateLastPointer =
-                tracker.isOldestTrackerInQueue() && isPreviewEnabled();
-        if (!needsToUpdateLastPointer) {
+        if (!isPreviewEnabled()) {
             return;
         }
         tracker.getLastCoordinates(mLastPointerCoords);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
index 70363e6..f29ade8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java
@@ -21,8 +21,8 @@
 
 import com.android.inputmethod.latin.InputPointers;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResourceUtils;
 
 public class GestureStroke {
     private static final String TAG = GestureStroke.class.getSimpleName();
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index b31f00b..ecc67dd 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -19,7 +19,7 @@
 import android.content.res.TypedArray;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
 
 public final class GestureStrokeWithPreviewPoints extends GestureStroke {
     public static final int PREVIEW_CAPACITY = 256;
@@ -58,7 +58,7 @@
         }
 
         private static double degreeToRadian(final int degree) {
-            return (double)degree / 180.0d * Math.PI;
+            return degree / 180.0d * Math.PI;
         }
 
         public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
@@ -125,8 +125,18 @@
 
     }
 
+    /**
+     * Append sampled preview points.
+     *
+     * @param eventTimes the event time array of gesture trail to be drawn.
+     * @param xCoords the x-coordinates array of gesture trail to be drawn.
+     * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+     */
     public void appendPreviewStroke(final ResizableIntArray eventTimes,
-            final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+            final ResizableIntArray xCoords, final ResizableIntArray yCoords,
+            final ResizableIntArray types) {
         final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
         if (length <= 0) {
             return;
@@ -134,6 +144,9 @@
         eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
         xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
         yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+        if (GestureTrail.DEBUG_SHOW_POINTS) {
+            types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length);
+        }
         mLastPreviewSize = mPreviewEventTimes.getLength();
     }
 
@@ -148,6 +161,8 @@
      * @param eventTimes the event time array of gesture trail to be drawn.
      * @param xCoords the x-coordinates array of gesture trail to be drawn.
      * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
      * @return the start index of the last interpolated segment of input arrays.
      */
     public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
@@ -189,7 +204,7 @@
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
                 yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
-                if (GestureTrail.DBG_SHOW_POINTS) {
+                if (GestureTrail.DEBUG_SHOW_POINTS) {
                     types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED);
                 }
                 d1++;
@@ -197,7 +212,7 @@
             eventTimes.add(d1, pt[p2]);
             xCoords.add(d1, px[p2]);
             yCoords.add(d1, py[p2]);
-            if (GestureTrail.DBG_SHOW_POINTS) {
+            if (GestureTrail.DEBUG_SHOW_POINTS) {
                 types.add(d1, GestureTrail.POINT_TYPE_SAMPLED);
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
index 03dd1c3..aca6679 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
@@ -26,7 +26,7 @@
 
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResizableIntArray;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
 
 /*
  * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay
@@ -36,10 +36,11 @@
  * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
  */
 final class GestureTrail {
-    public static final boolean DBG_SHOW_POINTS = false;
-    public static final int POINT_TYPE_SAMPLED = 0;
-    public static final int POINT_TYPE_INTERPOLATED = 1;
-    public static final int POINT_TYPE_COMPROMISED = 2;
+    public static final boolean DEBUG_SHOW_POINTS = false;
+    public static final int POINT_TYPE_SAMPLED = 1;
+    public static final int POINT_TYPE_INTERPOLATED = 2;
+    private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
+    private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
 
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
@@ -48,7 +49,7 @@
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mPointTypes = new ResizableIntArray(
-            DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
+            DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
     private int mCurrentStrokeId = -1;
     // The wall time of the zero value in {@link #mEventTimes}
     private long mCurrentTimeBase;
@@ -83,10 +84,12 @@
                     R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
             mTrailShadowEnabled = (trailShadowRatioInt > 0);
             mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
-            mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
+            mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
@@ -117,7 +120,7 @@
 
     private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
         final int trailSize = mEventTimes.getLength();
-        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
         if (mEventTimes.getLength() == trailSize) {
             return;
         }
@@ -242,7 +245,7 @@
                     final float body1 = r1 * params.mTrailBodyRatio;
                     final float body2 = r2 * params.mTrailBodyRatio;
                     final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2);
-                    if (path != null) {
+                    if (!path.isEmpty()) {
                         roundedLine.getBounds(mRoundedLineBounds);
                         if (params.mTrailShadowEnabled) {
                             final float shadow2 = r2 * params.mTrailShadowRatio;
@@ -255,23 +258,15 @@
                         final int alpha = getAlpha(elapsedTime, params);
                         paint.setAlpha(alpha);
                         canvas.drawPath(path, paint);
-                        if (DBG_SHOW_POINTS) {
-                            if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
-                                paint.setColor(Color.RED);
-                            } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
-                                paint.setColor(0xFFA000FF);
-                            } else {
-                                paint.setColor(Color.GREEN);
-                            }
-                            canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
-                            paint.setColor(params.mTrailColor);
-                        }
                     }
                 }
                 p1x = p2x;
                 p1y = p2y;
                 r1 = r2;
             }
+            if (DEBUG_SHOW_POINTS) {
+                debugDrawPoints(canvas, startIndex, trailSize, paint);
+            }
         }
 
         final int newSize = trailSize - startIndex;
@@ -281,11 +276,14 @@
                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+                if (DEBUG_SHOW_POINTS) {
+                    System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize);
+                }
             }
             mEventTimes.setLength(newSize);
             mXCoordinates.setLength(newSize);
             mYCoordinates.setLength(newSize);
-            if (DBG_SHOW_POINTS) {
+            if (DEBUG_SHOW_POINTS) {
                 mPointTypes.setLength(newSize);
             }
             // The start index of the last segment of the stroke
@@ -295,4 +293,26 @@
         }
         return newSize > 0;
     }
+
+    private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex,
+            final Paint paint) {
+        final int[] xCoords = mXCoordinates.getPrimitiveArray();
+        final int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int[] pointTypes = mPointTypes.getPrimitiveArray();
+        // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel.
+        paint.setAntiAlias(false);
+        paint.setStrokeWidth(0);
+        for (int i = startIndex; i < endIndex; i++) {
+            final int pointType = pointTypes[i];
+            if (pointType == POINT_TYPE_INTERPOLATED) {
+                paint.setColor(Color.RED);
+            } else if (pointType == POINT_TYPE_SAMPLED) {
+                paint.setColor(0xFFA000FF);
+            } else {
+                paint.setColor(Color.GREEN);
+            }
+            canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint);
+        }
+        paint.setAntiAlias(true);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
index 1e4c43e..19e9955 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java
@@ -30,8 +30,8 @@
 
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.keyboard.internal.GestureTrail.Params;
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 
 /**
  * Draw gesture trail preview graphics during gesture.
@@ -104,7 +104,13 @@
         freeOffscreenBuffer();
     }
 
+    public void deallocateMemory() {
+        freeOffscreenBuffer();
+    }
+
     private void freeOffscreenBuffer() {
+        mOffscreenCanvas.setBitmap(null);
+        mOffscreenCanvas.setMatrix(null);
         if (mOffscreenBuffer != null) {
             mOffscreenBuffer.recycle();
             mOffscreenBuffer = null;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
index 0ec8153..b526a94 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.annotations.UsedForTesting;
-
 /**
  * Interpolates XY-coordinates using Cubic Hermite Curve.
  */
@@ -54,7 +52,6 @@
      * @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;
@@ -79,7 +76,6 @@
      *           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];
@@ -152,7 +148,6 @@
      *
      * @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;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
index 5dcd842..1716fa0 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java
@@ -18,7 +18,7 @@
 
 import android.graphics.Typeface;
 
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
 
 public final class KeyDrawParams {
     public Typeface mTypeface;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
index b1813a1..22f5b3d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java
@@ -21,10 +21,10 @@
 
 import android.text.TextUtils;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -53,7 +53,9 @@
     private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
 
     // Constants for parsing.
-    private static final char LABEL_END = '|';
+    private static final char COMMA = ',';
+    private static final char BACKSLASH = '\\';
+    private static final char VERTICAL_BAR = '|';
     private static final String PREFIX_TEXT = "!text/";
     static final String PREFIX_ICON = "!icon/";
     private static final String PREFIX_CODE = "!code/";
@@ -64,6 +66,59 @@
         // Intentional empty constructor for utility class.
     }
 
+    /**
+     * Split the text containing multiple key specifications separated by commas into an array of
+     * key specifications.
+     * A key specification can contain a character escaped by the backslash character, including a
+     * comma character.
+     * Note that an empty key specification will be eliminated from the result array.
+     *
+     * @param text the text containing multiple key specifications.
+     * @return an array of key specification text. Null if the specified <code>text</code> is empty
+     * or has no key specifications.
+     */
+    public static String[] splitKeySpecs(final String text) {
+        final int size = text.length();
+        if (size == 0) {
+            return null;
+        }
+        // Optimization for one-letter key specification.
+        if (size == 1) {
+            return text.charAt(0) == COMMA ? null : new String[] { text };
+        }
+
+        ArrayList<String> list = null;
+        int start = 0;
+        // The characters in question in this loop are COMMA and BACKSLASH. These characters never
+        // match any high or low surrogate character. So it is OK to iterate through with char
+        // index.
+        for (int pos = 0; pos < size; pos++) {
+            final char c = text.charAt(pos);
+            if (c == COMMA) {
+                // Skip empty entry.
+                if (pos - start > 0) {
+                    if (list == null) {
+                        list = CollectionUtils.newArrayList();
+                    }
+                    list.add(text.substring(start, pos));
+                }
+                // Skip comma
+                start = pos + 1;
+            } else if (c == BACKSLASH) {
+                // Skip escape character and escaped character.
+                pos++;
+            }
+        }
+        final String remain = (size - start > 0) ? text.substring(start) : null;
+        if (list == null) {
+            return remain != null ? new String[] { remain } : null;
+        }
+        if (remain != null) {
+            list.add(remain);
+        }
+        return list.toArray(new String[list.size()]);
+    }
+
     private static boolean hasIcon(final String moreKeySpec) {
         return moreKeySpec.startsWith(PREFIX_ICON);
     }
@@ -78,14 +133,14 @@
     }
 
     private static String parseEscape(final String text) {
-        if (text.indexOf(Constants.CSV_ESCAPE) < 0) {
+        if (text.indexOf(BACKSLASH) < 0) {
             return text;
         }
         final int length = text.length();
         final StringBuilder sb = new StringBuilder();
         for (int pos = 0; pos < length; pos++) {
             final char c = text.charAt(pos);
-            if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+            if (c == BACKSLASH && pos + 1 < length) {
                 // Skip escape char
                 pos++;
                 sb.append(text.charAt(pos));
@@ -97,20 +152,20 @@
     }
 
     private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
-        if (moreKeySpec.indexOf(Constants.CSV_ESCAPE, start) < 0) {
-            final int end = moreKeySpec.indexOf(LABEL_END, start);
+        if (moreKeySpec.indexOf(BACKSLASH, start) < 0) {
+            final int end = moreKeySpec.indexOf(VERTICAL_BAR, start);
             if (end == 0) {
-                throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
+                throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec);
             }
             return end;
         }
         final int length = moreKeySpec.length();
         for (int pos = start; pos < length; pos++) {
             final char c = moreKeySpec.charAt(pos);
-            if (c == Constants.CSV_ESCAPE && pos + 1 < length) {
+            if (c == BACKSLASH && pos + 1 < length) {
                 // Skip escape char
                 pos++;
-            } else if (c == LABEL_END) {
+            } else if (c == VERTICAL_BAR) {
                 return pos;
             }
         }
@@ -136,9 +191,9 @@
             return null;
         }
         if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
-            throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+            throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
         }
-        return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
+        return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1));
     }
 
     static String getOutputText(final String moreKeySpec) {
@@ -169,7 +224,7 @@
         if (hasCode(moreKeySpec)) {
             final int end = indexOfLabelEnd(moreKeySpec, 0);
             if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
-                throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
+                throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
             }
             return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
         }
@@ -204,7 +259,7 @@
 
     public static int getIconId(final String moreKeySpec) {
         if (moreKeySpec != null && hasIcon(moreKeySpec)) {
-            final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
+            final int end = moreKeySpec.indexOf(VERTICAL_BAR, PREFIX_ICON.length());
             final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
                     : moreKeySpec.substring(PREFIX_ICON.length(), end);
             return KeyboardIconsSet.getIconId(name);
@@ -351,7 +406,7 @@
                     final String name = text.substring(pos + prefixLen, end);
                     sb.append(textsSet.getText(name));
                     pos = end - 1;
-                } else if (c == Constants.CSV_ESCAPE) {
+                } else if (c == BACKSLASH) {
                     if (sb != null) {
                         // Append both escape character and escaped character.
                         sb.append(text.substring(pos, Math.min(pos + 2, size)));
@@ -366,7 +421,6 @@
                 text = sb.toString();
             }
         } while (sb != null);
-
         return text;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
index 5db3ebb..f650569 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java
@@ -18,8 +18,6 @@
 
 import android.content.res.TypedArray;
 
-import com.android.inputmethod.latin.StringUtils;
-
 public abstract class KeyStyle {
     private final KeyboardTextsSet mTextsSet;
 
@@ -42,7 +40,7 @@
     protected String[] parseStringArray(final TypedArray a, final int index) {
         if (a.hasValue(index)) {
             final String text = KeySpecParser.resolveTextReference(a.getString(index), mTextsSet);
-            return StringUtils.parseCsvString(text);
+            return KeySpecParser.splitKeySpecs(text);
         }
         return null;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
index a048ad0..6aab3e7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java
@@ -20,9 +20,9 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
index 6ddd2a6..7a2622c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java
@@ -21,7 +21,7 @@
 import android.util.SparseIntArray;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
 
 public final class KeyVisualAttributes {
     public final Typeface mTypeface;
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
index be178f5..b34d7c4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java
@@ -29,12 +29,12 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.XmlParseUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.latin.utils.XmlParseUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -279,13 +279,13 @@
             params.mTextsSet.setLanguage(language);
             final RunInLocale<Void> job = new RunInLocale<Void>() {
                 @Override
-                protected Void job(Resources res) {
+                protected Void job(final Resources res) {
                     params.mTextsSet.loadStringResources(mContext);
                     return null;
                 }
             };
             // Null means the current system locale.
-            final Locale locale = SubtypeLocale.isNoLanguage(params.mId.mSubtype)
+            final Locale locale = SubtypeLocaleUtils.isNoLanguage(params.mId.mSubtype)
                     ? null : params.mId.mLocale;
             job.runInLocale(mResources, locale);
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 3e25c3b..d65aae2 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -16,8 +16,8 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
@@ -43,6 +43,7 @@
         "key_enter",
         "key_space",
         "key_shift",
+        "key_capslock",
         "key_switch_alpha_symbol",
         "key_output_text",
         "key_delete",
@@ -79,6 +80,7 @@
         Constants.CODE_ENTER,
         Constants.CODE_SPACE,
         Constants.CODE_SHIFT,
+        Constants.CODE_CAPSLOCK,
         Constants.CODE_SWITCH_ALPHA_SYMBOL,
         Constants.CODE_OUTPUT_TEXT,
         Constants.CODE_DELETE,
@@ -116,6 +118,7 @@
         DEFAULT[12],
         DEFAULT[13],
         DEFAULT[14],
+        DEFAULT[15],
         CODE_RIGHT_PARENTHESIS,
         CODE_LEFT_PARENTHESIS,
         CODE_GREATER_THAN_SIGN,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
index 4ac2549..4e3f761 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java
@@ -22,8 +22,8 @@
 import android.util.Log;
 import android.util.SparseIntArray;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
index 15eb690..a57b83a 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java
@@ -20,8 +20,8 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.KeyboardId;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.TreeSet;
@@ -84,11 +84,16 @@
 
     public void onAddKey(final Key newKey) {
         final Key key = (mKeysCache != null) ? mKeysCache.get(newKey) : newKey;
-        final boolean zeroWidthSpacer = key.isSpacer() && key.mWidth == 0;
-        if (!zeroWidthSpacer) {
-            mKeys.add(key);
-            updateHistogram(key);
+        final boolean isSpacer = key.isSpacer();
+        if (isSpacer && key.mWidth == 0) {
+            // Ignore zero width {@link Spacer}.
+            return;
         }
+        mKeys.add(key);
+        if (isSpacer) {
+            return;
+        }
+        updateHistogram(key);
         if (key.mCode == Constants.CODE_SHIFT) {
             mShiftKeys.add(key);
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
index 855f655..5fe84a7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java
@@ -23,7 +23,7 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 6af1bd7..164910d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -20,7 +20,7 @@
 import android.util.Log;
 
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 
 /**
  * Keyboard state machine.
@@ -29,8 +29,8 @@
  *
  * The input events are {@link #onLoadKeyboard()}, {@link #onSaveKeyboardState()},
  * {@link #onPressKey(int,boolean,int)}, {@link #onReleaseKey(int,boolean)},
- * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()}, {@link #onCancelInput()},
- * {@link #onUpdateShiftState(int,int)}, {@link #onLongPressTimeout(int)}.
+ * {@link #onCodeInput(int,int)}, {@link #onFinishSlidingInput()},
+ * {@link #onUpdateShiftState(int,int)}, {@link #onResetKeyboardStateToAlphabet()}.
  *
  * The actions are {@link SwitchActions}'s methods.
  */
@@ -53,12 +53,9 @@
          */
         public void requestUpdatingShiftState();
 
-        public void startDoubleTapTimer();
-        public boolean isInDoubleTapTimeout();
-        public void cancelDoubleTapTimer();
-        public void startLongPressTimer(int code);
-        public void cancelLongPressTimer();
-        public void hapticAndAudioFeedback(int code);
+        public void startDoubleTapShiftKeyTimer();
+        public boolean isInDoubleTapShiftKeyTimeout();
+        public void cancelDoubleTapShiftKeyTimer();
     }
 
     private final SwitchActions mSwitchActions;
@@ -84,9 +81,6 @@
     private boolean mPrevSymbolsKeyboardWasShifted;
     private int mRecapitalizeMode;
 
-    // For handling long press.
-    private boolean mLongPressShiftLockFired;
-
     // For handling double tap.
     private boolean mIsInAlphabetUnshiftedFromShifted;
     private boolean mIsInDoubleTapShiftKey;
@@ -321,14 +315,18 @@
             Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code)
                    + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this);
         }
+        if (code != Constants.CODE_SHIFT) {
+            // Because the double tap shift key timer is to detect two consecutive shift key press,
+            // it should be canceled when a non-shift key is pressed.
+            mSwitchActions.cancelDoubleTapShiftKeyTimer();
+        }
         if (code == Constants.CODE_SHIFT) {
             onPressShift();
+        } else if (code == Constants.CODE_CAPSLOCK) {
+            // Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
         } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
             onPressSymbol();
         } else {
-            mSwitchActions.cancelDoubleTapTimer();
-            mSwitchActions.cancelLongPressTimer();
-            mLongPressShiftLockFired = false;
             mShiftKeyState.onOtherKeyPressed();
             mSymbolKeyState.onOtherKeyPressed();
             // It is required to reset the auto caps state when all of the following conditions
@@ -356,6 +354,8 @@
         }
         if (code == Constants.CODE_SHIFT) {
             onReleaseShift(withSliding);
+        } else if (code == Constants.CODE_CAPSLOCK) {
+            setShiftLocked(!mAlphabetShiftState.isShiftLocked());
         } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
             onReleaseSymbol(withSliding);
         }
@@ -381,16 +381,6 @@
         mSymbolKeyState.onRelease();
     }
 
-    public void onLongPressTimeout(final int code) {
-        if (DEBUG_EVENT) {
-            Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this);
-        }
-        if (mIsAlphabetMode && code == Constants.CODE_SHIFT) {
-            mLongPressShiftLockFired = true;
-            mSwitchActions.hapticAndAudioFeedback(code);
-        }
-    }
-
     public void onUpdateShiftState(final int autoCaps, final int recapitalizeMode) {
         if (DEBUG_EVENT) {
             Log.d(TAG, "onUpdateShiftState: autoCaps=" + autoCaps + ", recapitalizeMode="
@@ -447,15 +437,16 @@
     }
 
     private void onPressShift() {
-        mLongPressShiftLockFired = false;
         // If we are recapitalizing, we don't do any of the normal processing, including
         // importantly the double tap timer.
-        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) return;
+        if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
+            return;
+        }
         if (mIsAlphabetMode) {
-            mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapTimeout();
+            mIsInDoubleTapShiftKey = mSwitchActions.isInDoubleTapShiftKeyTimeout();
             if (!mIsInDoubleTapShiftKey) {
                 // This is first tap.
-                mSwitchActions.startDoubleTapTimer();
+                mSwitchActions.startDoubleTapShiftKeyTimer();
             }
             if (mIsInDoubleTapShiftKey) {
                 if (mAlphabetShiftState.isManualShifted() || mIsInAlphabetUnshiftedFromShifted) {
@@ -486,7 +477,6 @@
                     setShifted(MANUAL_SHIFT);
                     mShiftKeyState.onPress();
                 }
-                mSwitchActions.startLongPressTimer(Constants.CODE_SHIFT);
             }
         } else {
             // In symbol mode, just toggle symbol and symbol more keyboard.
@@ -508,8 +498,6 @@
                 // Double tap shift key has been handled in {@link #onPressShift}, so that just
                 // ignore this release shift key here.
                 mIsInDoubleTapShiftKey = false;
-            } else if (mLongPressShiftLockFired) {
-                setShiftLocked(!mAlphabetShiftState.isShiftLocked());
             } else if (mShiftKeyState.isChording()) {
                 if (mAlphabetShiftState.isShiftLockShifted()) {
                     // After chording input while shift locked state.
@@ -576,11 +564,6 @@
         }
     }
 
-    public boolean isInMomentarySwitchState() {
-        return mSwitchState == SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL
-                || mSwitchState == SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE;
-    }
-
     private static boolean isSpaceCharacter(final int c) {
         return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 711cad6..7bb7442 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
@@ -133,122 +133,125 @@
         /* 28 */ "keylabel_for_east_slavic_row2_11",
         /* 29 */ "keylabel_for_east_slavic_row3_5",
         /* 30 */ "more_keys_for_cyrillic_u",
-        /* 31 */ "more_keys_for_cyrillic_en",
-        /* 32 */ "more_keys_for_cyrillic_ghe",
-        /* 33 */ "more_keys_for_east_slavic_row2_1",
-        /* 34 */ "more_keys_for_cyrillic_o",
-        /* 35 */ "more_keys_for_cyrillic_soft_sign",
-        /* 36 */ "keylabel_for_south_slavic_row1_6",
-        /* 37 */ "keylabel_for_south_slavic_row2_11",
-        /* 38 */ "keylabel_for_south_slavic_row3_1",
-        /* 39 */ "keylabel_for_south_slavic_row3_8",
-        /* 40 */ "more_keys_for_cyrillic_ie",
-        /* 41 */ "more_keys_for_cyrillic_i",
-        /* 42 */ "label_to_alpha_key",
-        /* 43 */ "single_quotes",
-        /* 44 */ "double_quotes",
-        /* 45 */ "single_angle_quotes",
-        /* 46 */ "double_angle_quotes",
-        /* 47 */ "more_keys_for_currency_dollar",
-        /* 48 */ "keylabel_for_currency_generic",
-        /* 49 */ "more_keys_for_currency_generic",
-        /* 50 */ "more_keys_for_punctuation",
-        /* 51 */ "more_keys_for_star",
-        /* 52 */ "more_keys_for_bullet",
-        /* 53 */ "more_keys_for_plus",
-        /* 54 */ "more_keys_for_left_parenthesis",
-        /* 55 */ "more_keys_for_right_parenthesis",
-        /* 56 */ "more_keys_for_less_than",
-        /* 57 */ "more_keys_for_greater_than",
-        /* 58 */ "more_keys_for_arabic_diacritics",
-        /* 59 */ "keyhintlabel_for_arabic_diacritics",
-        /* 60 */ "keylabel_for_symbols_1",
-        /* 61 */ "keylabel_for_symbols_2",
-        /* 62 */ "keylabel_for_symbols_3",
-        /* 63 */ "keylabel_for_symbols_4",
-        /* 64 */ "keylabel_for_symbols_5",
-        /* 65 */ "keylabel_for_symbols_6",
-        /* 66 */ "keylabel_for_symbols_7",
-        /* 67 */ "keylabel_for_symbols_8",
-        /* 68 */ "keylabel_for_symbols_9",
-        /* 69 */ "keylabel_for_symbols_0",
-        /* 70 */ "label_to_symbol_key",
-        /* 71 */ "label_to_symbol_with_microphone_key",
-        /* 72 */ "additional_more_keys_for_symbols_1",
-        /* 73 */ "additional_more_keys_for_symbols_2",
-        /* 74 */ "additional_more_keys_for_symbols_3",
-        /* 75 */ "additional_more_keys_for_symbols_4",
-        /* 76 */ "additional_more_keys_for_symbols_5",
-        /* 77 */ "additional_more_keys_for_symbols_6",
-        /* 78 */ "additional_more_keys_for_symbols_7",
-        /* 79 */ "additional_more_keys_for_symbols_8",
-        /* 80 */ "additional_more_keys_for_symbols_9",
-        /* 81 */ "additional_more_keys_for_symbols_0",
-        /* 82 */ "more_keys_for_symbols_1",
-        /* 83 */ "more_keys_for_symbols_2",
-        /* 84 */ "more_keys_for_symbols_3",
-        /* 85 */ "more_keys_for_symbols_4",
-        /* 86 */ "more_keys_for_symbols_5",
-        /* 87 */ "more_keys_for_symbols_6",
-        /* 88 */ "more_keys_for_symbols_7",
-        /* 89 */ "more_keys_for_symbols_8",
-        /* 90 */ "more_keys_for_symbols_9",
-        /* 91 */ "more_keys_for_symbols_0",
-        /* 92 */ "keylabel_for_comma",
-        /* 93 */ "more_keys_for_comma",
-        /* 94 */ "keylabel_for_symbols_question",
-        /* 95 */ "keylabel_for_symbols_semicolon",
-        /* 96 */ "keylabel_for_symbols_percent",
-        /* 97 */ "more_keys_for_symbols_exclamation",
-        /* 98 */ "more_keys_for_symbols_question",
-        /* 99 */ "more_keys_for_symbols_semicolon",
-        /* 100 */ "more_keys_for_symbols_percent",
-        /* 101 */ "keylabel_for_tablet_comma",
-        /* 102 */ "keyhintlabel_for_tablet_comma",
-        /* 103 */ "more_keys_for_tablet_comma",
-        /* 104 */ "keyhintlabel_for_tablet_period",
-        /* 105 */ "more_keys_for_tablet_period",
-        /* 106 */ "keylabel_for_apostrophe",
-        /* 107 */ "keyhintlabel_for_apostrophe",
-        /* 108 */ "more_keys_for_apostrophe",
-        /* 109 */ "more_keys_for_q",
-        /* 110 */ "more_keys_for_x",
-        /* 111 */ "keylabel_for_q",
-        /* 112 */ "keylabel_for_w",
-        /* 113 */ "keylabel_for_y",
-        /* 114 */ "keylabel_for_x",
-        /* 115 */ "keylabel_for_spanish_row2_10",
-        /* 116 */ "more_keys_for_am_pm",
-        /* 117 */ "settings_as_more_key",
-        /* 118 */ "shortcut_as_more_key",
-        /* 119 */ "action_next_as_more_key",
-        /* 120 */ "action_previous_as_more_key",
-        /* 121 */ "label_to_more_symbol_key",
-        /* 122 */ "label_to_more_symbol_for_tablet_key",
-        /* 123 */ "label_tab_key",
-        /* 124 */ "label_to_phone_numeric_key",
-        /* 125 */ "label_to_phone_symbols_key",
-        /* 126 */ "label_time_am",
-        /* 127 */ "label_time_pm",
-        /* 128 */ "label_to_symbol_key_pcqwerty",
-        /* 129 */ "keylabel_for_popular_domain",
-        /* 130 */ "more_keys_for_popular_domain",
-        /* 131 */ "more_keys_for_smiley",
-        /* 132 */ "single_laqm_raqm",
-        /* 133 */ "single_laqm_raqm_rtl",
-        /* 134 */ "single_raqm_laqm",
-        /* 135 */ "double_laqm_raqm",
-        /* 136 */ "double_laqm_raqm_rtl",
-        /* 137 */ "double_raqm_laqm",
-        /* 138 */ "single_lqm_rqm",
-        /* 139 */ "single_9qm_lqm",
-        /* 140 */ "single_9qm_rqm",
-        /* 141 */ "double_lqm_rqm",
-        /* 142 */ "double_9qm_lqm",
-        /* 143 */ "double_9qm_rqm",
-        /* 144 */ "more_keys_for_single_quote",
-        /* 145 */ "more_keys_for_double_quote",
-        /* 146 */ "more_keys_for_tablet_double_quote",
+        /* 31 */ "more_keys_for_cyrillic_ka",
+        /* 32 */ "more_keys_for_cyrillic_en",
+        /* 33 */ "more_keys_for_cyrillic_ghe",
+        /* 34 */ "more_keys_for_east_slavic_row2_1",
+        /* 35 */ "more_keys_for_cyrillic_a",
+        /* 36 */ "more_keys_for_cyrillic_o",
+        /* 37 */ "more_keys_for_cyrillic_soft_sign",
+        /* 38 */ "more_keys_for_east_slavic_row2_11",
+        /* 39 */ "keylabel_for_south_slavic_row1_6",
+        /* 40 */ "keylabel_for_south_slavic_row2_11",
+        /* 41 */ "keylabel_for_south_slavic_row3_1",
+        /* 42 */ "keylabel_for_south_slavic_row3_8",
+        /* 43 */ "more_keys_for_cyrillic_ie",
+        /* 44 */ "more_keys_for_cyrillic_i",
+        /* 45 */ "label_to_alpha_key",
+        /* 46 */ "single_quotes",
+        /* 47 */ "double_quotes",
+        /* 48 */ "single_angle_quotes",
+        /* 49 */ "double_angle_quotes",
+        /* 50 */ "more_keys_for_currency_dollar",
+        /* 51 */ "keylabel_for_currency_generic",
+        /* 52 */ "more_keys_for_currency_generic",
+        /* 53 */ "more_keys_for_punctuation",
+        /* 54 */ "more_keys_for_star",
+        /* 55 */ "more_keys_for_bullet",
+        /* 56 */ "more_keys_for_plus",
+        /* 57 */ "more_keys_for_left_parenthesis",
+        /* 58 */ "more_keys_for_right_parenthesis",
+        /* 59 */ "more_keys_for_less_than",
+        /* 60 */ "more_keys_for_greater_than",
+        /* 61 */ "more_keys_for_arabic_diacritics",
+        /* 62 */ "keyhintlabel_for_arabic_diacritics",
+        /* 63 */ "keylabel_for_symbols_1",
+        /* 64 */ "keylabel_for_symbols_2",
+        /* 65 */ "keylabel_for_symbols_3",
+        /* 66 */ "keylabel_for_symbols_4",
+        /* 67 */ "keylabel_for_symbols_5",
+        /* 68 */ "keylabel_for_symbols_6",
+        /* 69 */ "keylabel_for_symbols_7",
+        /* 70 */ "keylabel_for_symbols_8",
+        /* 71 */ "keylabel_for_symbols_9",
+        /* 72 */ "keylabel_for_symbols_0",
+        /* 73 */ "label_to_symbol_key",
+        /* 74 */ "label_to_symbol_with_microphone_key",
+        /* 75 */ "additional_more_keys_for_symbols_1",
+        /* 76 */ "additional_more_keys_for_symbols_2",
+        /* 77 */ "additional_more_keys_for_symbols_3",
+        /* 78 */ "additional_more_keys_for_symbols_4",
+        /* 79 */ "additional_more_keys_for_symbols_5",
+        /* 80 */ "additional_more_keys_for_symbols_6",
+        /* 81 */ "additional_more_keys_for_symbols_7",
+        /* 82 */ "additional_more_keys_for_symbols_8",
+        /* 83 */ "additional_more_keys_for_symbols_9",
+        /* 84 */ "additional_more_keys_for_symbols_0",
+        /* 85 */ "more_keys_for_symbols_1",
+        /* 86 */ "more_keys_for_symbols_2",
+        /* 87 */ "more_keys_for_symbols_3",
+        /* 88 */ "more_keys_for_symbols_4",
+        /* 89 */ "more_keys_for_symbols_5",
+        /* 90 */ "more_keys_for_symbols_6",
+        /* 91 */ "more_keys_for_symbols_7",
+        /* 92 */ "more_keys_for_symbols_8",
+        /* 93 */ "more_keys_for_symbols_9",
+        /* 94 */ "more_keys_for_symbols_0",
+        /* 95 */ "keylabel_for_comma",
+        /* 96 */ "more_keys_for_comma",
+        /* 97 */ "keylabel_for_symbols_question",
+        /* 98 */ "keylabel_for_symbols_semicolon",
+        /* 99 */ "keylabel_for_symbols_percent",
+        /* 100 */ "more_keys_for_symbols_exclamation",
+        /* 101 */ "more_keys_for_symbols_question",
+        /* 102 */ "more_keys_for_symbols_semicolon",
+        /* 103 */ "more_keys_for_symbols_percent",
+        /* 104 */ "keylabel_for_tablet_comma",
+        /* 105 */ "keyhintlabel_for_tablet_comma",
+        /* 106 */ "more_keys_for_tablet_comma",
+        /* 107 */ "keyhintlabel_for_tablet_period",
+        /* 108 */ "more_keys_for_tablet_period",
+        /* 109 */ "keylabel_for_apostrophe",
+        /* 110 */ "keyhintlabel_for_apostrophe",
+        /* 111 */ "more_keys_for_apostrophe",
+        /* 112 */ "more_keys_for_q",
+        /* 113 */ "more_keys_for_x",
+        /* 114 */ "keylabel_for_q",
+        /* 115 */ "keylabel_for_w",
+        /* 116 */ "keylabel_for_y",
+        /* 117 */ "keylabel_for_x",
+        /* 118 */ "keylabel_for_spanish_row2_10",
+        /* 119 */ "more_keys_for_am_pm",
+        /* 120 */ "settings_as_more_key",
+        /* 121 */ "shortcut_as_more_key",
+        /* 122 */ "action_next_as_more_key",
+        /* 123 */ "action_previous_as_more_key",
+        /* 124 */ "label_to_more_symbol_key",
+        /* 125 */ "label_to_more_symbol_for_tablet_key",
+        /* 126 */ "label_tab_key",
+        /* 127 */ "label_to_phone_numeric_key",
+        /* 128 */ "label_to_phone_symbols_key",
+        /* 129 */ "label_time_am",
+        /* 130 */ "label_time_pm",
+        /* 131 */ "label_to_symbol_key_pcqwerty",
+        /* 132 */ "keylabel_for_popular_domain",
+        /* 133 */ "more_keys_for_popular_domain",
+        /* 134 */ "more_keys_for_smiley",
+        /* 135 */ "single_laqm_raqm",
+        /* 136 */ "single_laqm_raqm_rtl",
+        /* 137 */ "single_raqm_laqm",
+        /* 138 */ "double_laqm_raqm",
+        /* 139 */ "double_laqm_raqm_rtl",
+        /* 140 */ "double_raqm_laqm",
+        /* 141 */ "single_lqm_rqm",
+        /* 142 */ "single_9qm_lqm",
+        /* 143 */ "single_9qm_rqm",
+        /* 144 */ "double_lqm_rqm",
+        /* 145 */ "double_9qm_lqm",
+        /* 146 */ "double_9qm_rqm",
+        /* 147 */ "more_keys_for_single_quote",
+        /* 148 */ "more_keys_for_double_quote",
+        /* 149 */ "more_keys_for_tablet_double_quote",
     };
 
     private static final String EMPTY = "";
@@ -259,146 +262,146 @@
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        EMPTY, EMPTY, EMPTY,
-        /* ~41 */
+        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
-        /* 42 */ "ABC",
-        /* 43 */ "!text/single_lqm_rqm",
-        /* 44 */ "!text/double_lqm_rqm",
-        /* 45 */ "!text/single_laqm_raqm",
-        /* 46 */ "!text/double_laqm_raqm",
+        /* 45 */ "ABC",
+        /* 46 */ "!text/single_lqm_rqm",
+        /* 47 */ "!text/double_lqm_rqm",
+        /* 48 */ "!text/single_laqm_raqm",
+        /* 49 */ "!text/double_laqm_raqm",
         // U+00A2: "¢" CENT SIGN
         // U+00A3: "£" POUND SIGN
         // U+20AC: "€" EURO SIGN
         // U+00A5: "¥" YEN SIGN
         // U+20B1: "₱" PESO SIGN
-        /* 47 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
-        /* 48 */ "$",
-        /* 49 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+        /* 51 */ "$",
+        /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
         // U+2020: "†" DAGGER
         // U+2021: "‡" DOUBLE DAGGER
         // U+2605: "★" BLACK STAR
-        /* 51 */ "\u2020,\u2021,\u2605",
+        /* 54 */ "\u2020,\u2021,\u2605",
         // U+266A: "♪" EIGHTH NOTE
         // U+2665: "♥" BLACK HEART SUIT
         // U+2660: "♠" BLACK SPADE SUIT
         // U+2666: "♦" BLACK DIAMOND SUIT
         // U+2663: "♣" BLACK CLUB SUIT
-        /* 52 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+        /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
         // U+00B1: "±" PLUS-MINUS SIGN
-        /* 53 */ "\u00B1",
+        /* 56 */ "\u00B1",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 54 */ "!fixedColumnOrder!3,<,{,[",
-        /* 55 */ "!fixedColumnOrder!3,>,},]",
+        /* 57 */ "!fixedColumnOrder!3,<,{,[",
+        /* 58 */ "!fixedColumnOrder!3,>,},]",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
-        /* 58 */ EMPTY,
-        /* 59 */ EMPTY,
-        /* 60 */ "1",
-        /* 61 */ "2",
-        /* 62 */ "3",
-        /* 63 */ "4",
-        /* 64 */ "5",
-        /* 65 */ "6",
-        /* 66 */ "7",
-        /* 67 */ "8",
-        /* 68 */ "9",
-        /* 69 */ "0",
+        /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
+        /* 61 */ EMPTY,
+        /* 62 */ EMPTY,
+        /* 63 */ "1",
+        /* 64 */ "2",
+        /* 65 */ "3",
+        /* 66 */ "4",
+        /* 67 */ "5",
+        /* 68 */ "6",
+        /* 69 */ "7",
+        /* 70 */ "8",
+        /* 71 */ "9",
+        /* 72 */ "0",
         // Label for "switch to symbols" key.
-        /* 70 */ "?123",
+        /* 73 */ "?123",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "123",
-        /* 72~ */
+        /* 74 */ "123",
+        /* 75~ */
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        /* ~81 */
+        /* ~84 */
         // U+00B9: "¹" SUPERSCRIPT ONE
         // U+00BD: "½" VULGAR FRACTION ONE HALF
         // U+2153: "⅓" VULGAR FRACTION ONE THIRD
         // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
         // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
-        /* 82 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+        /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
         // U+00B2: "²" SUPERSCRIPT TWO
         // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
-        /* 83 */ "\u00B2,\u2154",
+        /* 86 */ "\u00B2,\u2154",
         // U+00B3: "³" SUPERSCRIPT THREE
         // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
         // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
-        /* 84 */ "\u00B3,\u00BE,\u215C",
+        /* 87 */ "\u00B3,\u00BE,\u215C",
         // U+2074: "⁴" SUPERSCRIPT FOUR
-        /* 85 */ "\u2074",
+        /* 88 */ "\u2074",
         // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
-        /* 86 */ "\u215D",
-        /* 87 */ EMPTY,
-        // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
-        /* 88 */ "\u215E",
-        /* 89 */ EMPTY,
+        /* 89 */ "\u215D",
         /* 90 */ EMPTY,
+        // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
+        /* 91 */ "\u215E",
+        /* 92 */ EMPTY,
+        /* 93 */ EMPTY,
         // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
         // U+2205: "∅" EMPTY SET
-        /* 91 */ "\u207F,\u2205",
-        /* 92 */ ",",
-        /* 93 */ EMPTY,
-        /* 94 */ "?",
-        /* 95 */ ";",
-        /* 96 */ "%",
+        /* 94 */ "\u207F,\u2205",
+        /* 95 */ ",",
+        /* 96 */ EMPTY,
+        /* 97 */ "?",
+        /* 98 */ ";",
+        /* 99 */ "%",
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 97 */ "\u00A1",
+        /* 100 */ "\u00A1",
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 98 */ "\u00BF",
-        /* 99 */ EMPTY,
+        /* 101 */ "\u00BF",
+        /* 102 */ EMPTY,
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\u2030",
-        /* 101 */ ",",
-        /* 102 */ "!",
-        /* 103 */ "!",
-        /* 104 */ "?",
-        /* 105 */ "?",
-        /* 106 */ "\'",
-        /* 107 */ "\"",
-        /* 108 */ "\"",
-        /* 109 */ EMPTY,
-        /* 110 */ EMPTY,
-        /* 111 */ "q",
-        /* 112 */ "w",
-        /* 113 */ "y",
-        /* 114 */ "x",
-        /* 115 */ EMPTY,
-        /* 116 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
-        /* 117 */ "!icon/settings_key|!code/key_settings",
-        /* 118 */ "!icon/shortcut_key|!code/key_shortcut",
-        /* 119 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
-        /* 120 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+        /* 103 */ "\u2030",
+        /* 104 */ ",",
+        /* 105 */ "!",
+        /* 106 */ "!",
+        /* 107 */ "?",
+        /* 108 */ "?",
+        /* 109 */ "\'",
+        /* 110 */ "\"",
+        /* 111 */ "\"",
+        /* 112 */ EMPTY,
+        /* 113 */ EMPTY,
+        /* 114 */ "q",
+        /* 115 */ "w",
+        /* 116 */ "y",
+        /* 117 */ "x",
+        /* 118 */ EMPTY,
+        /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+        /* 120 */ "!icon/settings_key|!code/key_settings",
+        /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
+        /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+        /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
-        /* 121 */ "= \\ <",
+        /* 124 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 122 */ "~ \\ {",
+        /* 125 */ "~ \\ {",
         // Label for "Tab" key.  Must be short to fit on key!
-        /* 123 */ "Tab",
+        /* 126 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
-        /* 124 */ "123",
+        /* 127 */ "123",
         // Label for "switch to phone symbols" key.  Must be short to fit on key!
         // U+FF0A: "*" FULLWIDTH ASTERISK
         // U+FF03: "#" FULLWIDTH NUMBER SIGN
-        /* 125 */ "\uFF0A\uFF03",
+        /* 128 */ "\uFF0A\uFF03",
         // Key label for "ante meridiem"
-        /* 126 */ "AM",
+        /* 129 */ "AM",
         // Key label for "post meridiem"
-        /* 127 */ "PM",
+        /* 130 */ "PM",
         // Label for "switch to symbols" key on PC QWERTY layout
-        /* 128 */ "Sym",
-        /* 129 */ ".com",
+        /* 131 */ "Sym",
+        /* 132 */ ".com",
         // popular web domains for the locale - most popular, displayed on the keyboard
-        /* 130 */ "!hasLabels!,.net,.org,.gov,.edu",
-        /* 131 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+        /* 133 */ "!hasLabels!,.net,.org,.gov,.edu",
+        /* 134 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -420,24 +423,24 @@
         // The following each quotation mark pair consist of
         // <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 132 */ "\u2039,\u203A",
-        /* 133 */ "\u2039|\u203A,\u203A|\u2039",
-        /* 134 */ "\u203A,\u2039",
-        /* 135 */ "\u00AB,\u00BB",
-        /* 136 */ "\u00AB|\u00BB,\u00BB|\u00AB",
-        /* 137 */ "\u00BB,\u00AB",
+        /* 135 */ "\u2039,\u203A",
+        /* 136 */ "\u2039|\u203A,\u203A|\u2039",
+        /* 137 */ "\u203A,\u2039",
+        /* 138 */ "\u00AB,\u00BB",
+        /* 139 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 140 */ "\u00BB,\u00AB",
         // The following each quotation mark triplet consists of
         // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 138 */ "\u201A,\u2018,\u2019",
-        /* 139 */ "\u2019,\u201A,\u2018",
-        /* 140 */ "\u2018,\u201A,\u2019",
-        /* 141 */ "\u201E,\u201C,\u201D",
-        /* 142 */ "\u201D,\u201E,\u201C",
-        /* 143 */ "\u201C,\u201E,\u201D",
-        /* 144 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
-        /* 145 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
-        /* 146 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+        /* 141 */ "\u201A,\u2018,\u2019",
+        /* 142 */ "\u2019,\u201A,\u2018",
+        /* 143 */ "\u2018,\u201A,\u2019",
+        /* 144 */ "\u201E,\u201C,\u201D",
+        /* 145 */ "\u201D,\u201E,\u201C",
+        /* 146 */ "\u201C,\u201E,\u201D",
+        /* 147 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+        /* 148 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+        /* 149 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
     };
 
     /* Language af: Afrikaans */
@@ -498,45 +501,45 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0623: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+062C: "پ" ARABIC LETTER PEH
-        /* 42 */ "\u0623\u200C\u0628\u200C\u062C",
-        /* 43 */ null,
-        /* 44 */ null,
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
+        /* 46 */ null,
+        /* 47 */ null,
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null,
-        /* ~49 */
+        /* ~52 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 51 */ "\u2605,\u066D",
+        /* 54 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 52 */ "\u266A",
-        /* 53 */ null,
+        /* 55 */ "\u266A",
+        /* 56 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0654: "ٔ" ARABIC HAMZA ABOVE
         // U+0652: "ْ" ARABIC SUKUN
@@ -553,70 +556,116 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 58 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 59 */ "\u0651",
+        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 62 */ "\u0651",
         // U+0661: "١" ARABIC-INDIC DIGIT ONE
-        /* 60 */ "\u0661",
+        /* 63 */ "\u0661",
         // U+0662: "٢" ARABIC-INDIC DIGIT TWO
-        /* 61 */ "\u0662",
+        /* 64 */ "\u0662",
         // U+0663: "٣" ARABIC-INDIC DIGIT THREE
-        /* 62 */ "\u0663",
+        /* 65 */ "\u0663",
         // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
-        /* 63 */ "\u0664",
+        /* 66 */ "\u0664",
         // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
-        /* 64 */ "\u0665",
+        /* 67 */ "\u0665",
         // U+0666: "٦" ARABIC-INDIC DIGIT SIX
-        /* 65 */ "\u0666",
+        /* 68 */ "\u0666",
         // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
-        /* 66 */ "\u0667",
+        /* 69 */ "\u0667",
         // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
-        /* 67 */ "\u0668",
+        /* 70 */ "\u0668",
         // U+0669: "٩" ARABIC-INDIC DIGIT NINE
-        /* 68 */ "\u0669",
+        /* 71 */ "\u0669",
         // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
-        /* 69 */ "\u0660",
+        /* 72 */ "\u0660",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 70 */ "\u0663\u0662\u0661\u061F",
+        /* 73 */ "\u0663\u0662\u0661\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u0663\u0662\u0661",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
+        /* 74 */ "\u0663\u0662\u0661",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 81 */ "0,\u066B,\u066C",
-        /* 82~ */
+        /* 84 */ "0,\u066B,\u066C",
+        /* 85~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~91 */
+        /* ~94 */
         // U+060C: "،" ARABIC COMMA
-        /* 92 */ "\u060C",
-        /* 93 */ "\\,",
-        /* 94 */ "\u061F",
-        /* 95 */ "\u061B",
+        /* 95 */ "\u060C",
+        /* 96 */ "\\,",
+        /* 97 */ "\u061F",
+        /* 98 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 96 */ "\u066A",
-        /* 97 */ null,
-        /* 98 */ "?",
-        /* 99 */ ";",
+        /* 99 */ "\u066A",
+        /* 100 */ null,
+        /* 101 */ "?",
+        /* 102 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\\%,\u2030",
-        /* 101~ */
+        /* 103 */ "\\%,\u2030",
+        /* 104~ */
         null, null, null, null, null,
-        /* ~105 */
+        /* ~108 */
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 106 */ "\u060C",
-        /* 107 */ "\u061F",
-        /* 108 */ "\u061F,\u061B,!,:,-,/,\',\"",
+        /* 109 */ "\u060C",
+        /* 110 */ "\u061F",
+        /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
+    };
+
+    /* Language az: Azerbaijani */
+    private static final String[] LANGUAGE_az = {
+        // U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX
+        /* 0 */ "\u00E2",
+        // U+0259: "ə" LATIN SMALL LETTER SCHWA
+        /* 1 */ "\u0259",
+        // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+        // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+        // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+        // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+        // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+        // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+        /* 2 */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+        // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+        // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+        // U+0153: "œ" LATIN SMALL LIGATURE OE
+        // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+        // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+        // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+        // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+        /* 3 */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+        // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+        // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+        // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+        // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+        /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+        // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+        // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+        // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+        // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+        /* 5 */ "\u015F,\u00DF,\u015B,\u0161",
+        /* 6 */ null,
+        // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+        // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+        // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+        /* 7 */ "\u00E7,\u0107,\u010D",
+        /* 8~ */
+        null, null, null, null, null, null, null,
+        /* ~14 */
+        // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+        /* 15 */ "\u011F",
     };
 
     /* Language be: Belarusian */
@@ -636,23 +685,23 @@
         // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
         /* 29 */ "\u0456",
         /* 30~ */
-        null, null, null, null, null,
-        /* ~34 */
+        null, null, null, null, null, null, null,
+        /* ~36 */
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language bg: Bulgarian */
@@ -660,16 +709,16 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ null,
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ null,
         // single_quotes of Bulgarian is default single_quotes_right_left.
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language ca: Catalan */
@@ -733,22 +782,22 @@
         /* 15~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null,
-        /* ~49 */
+        null, null, null, null, null, null, null, null,
+        /* ~52 */
         // U+00B7: "·" MIDDLE DOT
-        /* 50 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
-        /* 51~ */
+        /* 53 */ "!fixedColumnOrder!9,\u00B7,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 54~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null,
-        /* ~104 */
-        /* 105 */ "?,\u00B7",
-        /* 106~ */
+        /* ~107 */
+        /* 108 */ "?,\u00B7",
+        /* 109~ */
         null, null, null, null, null, null, null, null, null,
-        /* ~114 */
+        /* ~117 */
         // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
-        /* 115 */ "\u00E7",
+        /* 118 */ "\u00E7",
     };
 
     /* Language cs: Czech */
@@ -822,11 +871,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language da: Danish */
@@ -890,12 +940,12 @@
         /* 24 */ "\u00F6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language de: German */
@@ -941,12 +991,12 @@
         /* 7~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language el: Greek */
@@ -954,13 +1004,13 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0391: "Α" GREEK CAPITAL LETTER ALPHA
         // U+0392: "Β" GREEK CAPITAL LETTER BETA
         // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
-        /* 42 */ "\u0391\u0392\u0393",
+        /* 45 */ "\u0391\u0392\u0393",
     };
 
     /* Language en: English */
@@ -1131,20 +1181,21 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~108 */
-        /* 109 */ "q",
-        /* 110 */ "x",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null,
+        /* ~111 */
+        /* 112 */ "q",
+        /* 113 */ "x",
         // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
-        /* 111 */ "\u015D",
+        /* 114 */ "\u015D",
         // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
-        /* 112 */ "\u011D",
+        /* 115 */ "\u011D",
         // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
-        /* 113 */ "\u016D",
+        /* 116 */ "\u016D",
         // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
-        /* 114 */ "\u0109",
+        /* 117 */ "\u0109",
         // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
-        /* 115 */ "\u0135",
+        /* 118 */ "\u0135",
     };
 
     /* Language es: Spanish */
@@ -1202,30 +1253,30 @@
         /* 8~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~49 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~52 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 50 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
-        /* 51~ */
+        /* 53 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
+        /* 54~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null,
-        /* ~102 */
+        /* ~105 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 103 */ "!,\u00A1",
-        /* 104 */ null,
+        /* 106 */ "!,\u00A1",
+        /* 107 */ null,
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 105 */ "?,\u00BF",
-        /* 106 */ "\"",
-        /* 107 */ "\'",
-        /* 108 */ "\'",
-        /* 109~ */
+        /* 108 */ "?,\u00BF",
+        /* 109 */ "\"",
+        /* 110 */ "\'",
+        /* 111 */ "\'",
+        /* 112~ */
         null, null, null, null, null, null,
-        /* ~114 */
+        /* ~117 */
         // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-        /* 115 */ "\u00F1",
+        /* 118 */ "\u00F1",
     };
 
     /* Language et: Estonian */
@@ -1328,10 +1379,10 @@
         /* 23 */ "\u00F5",
         /* 24~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language fa: Persian */
@@ -1339,45 +1390,45 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0627: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+067E: "پ" ARABIC LETTER PEH
-        /* 42 */ "\u0627\u200C\u0628\u200C\u067E",
-        /* 43 */ null,
-        /* 44 */ null,
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
+        /* 46 */ null,
+        /* 47 */ null,
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null,
-        /* ~49 */
+        /* ~52 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 51 */ "\u2605,\u066D",
+        /* 54 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 52 */ "\u266A",
-        /* 53 */ null,
+        /* 55 */ "\u266A",
+        /* 56 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0652: "ْ" ARABIC SUKUN
         // U+0651: "ّ" ARABIC SHADDA
@@ -1394,74 +1445,74 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 58 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 59 */ "\u064B",
+        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 62 */ "\u064B",
         // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
-        /* 60 */ "\u06F1",
+        /* 63 */ "\u06F1",
         // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
-        /* 61 */ "\u06F2",
+        /* 64 */ "\u06F2",
         // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
-        /* 62 */ "\u06F3",
+        /* 65 */ "\u06F3",
         // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
-        /* 63 */ "\u06F4",
+        /* 66 */ "\u06F4",
         // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
-        /* 64 */ "\u06F5",
+        /* 67 */ "\u06F5",
         // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
-        /* 65 */ "\u06F6",
+        /* 68 */ "\u06F6",
         // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
-        /* 66 */ "\u06F7",
+        /* 69 */ "\u06F7",
         // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
-        /* 67 */ "\u06F8",
+        /* 70 */ "\u06F8",
         // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
-        /* 68 */ "\u06F9",
+        /* 71 */ "\u06F9",
         // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
-        /* 69 */ "\u06F0",
+        /* 72 */ "\u06F0",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 70 */ "\u06F3\u06F2\u06F1\u061F",
+        /* 73 */ "\u06F3\u06F2\u06F1\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u06F3\u06F2\u06F1",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
+        /* 74 */ "\u06F3\u06F2\u06F1",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 81 */ "0,\u066B,\u066C",
-        /* 82~ */
+        /* 84 */ "0,\u066B,\u066C",
+        /* 85~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~91 */
+        /* ~94 */
         // U+060C: "،" ARABIC COMMA
-        /* 92 */ "\u060C",
-        /* 93 */ "\\,",
-        /* 94 */ "\u061F",
-        /* 95 */ "\u061B",
+        /* 95 */ "\u060C",
+        /* 96 */ "\\,",
+        /* 97 */ "\u061F",
+        /* 98 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 96 */ "\u066A",
-        /* 97 */ null,
-        /* 98 */ "?",
-        /* 99 */ ";",
+        /* 99 */ "\u066A",
+        /* 100 */ null,
+        /* 101 */ "?",
+        /* 102 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\\%,\u2030",
+        /* 103 */ "\\%,\u2030",
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 101 */ "\u060C",
-        /* 102 */ "!",
-        /* 103 */ "!,\\,",
-        /* 104 */ "\u061F",
-        /* 105 */ "\u061F,?",
-        /* 106 */ "\u060C",
+        /* 104 */ "\u060C",
+        /* 105 */ "!",
+        /* 106 */ "!,\\,",
         /* 107 */ "\u061F",
-        /* 108 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 108 */ "\u061F,?",
+        /* 109 */ "\u060C",
+        /* 110 */ "\u061F",
+        /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
     };
 
     /* Language fi: Finnish */
@@ -1569,56 +1620,56 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0915: "क" DEVANAGARI LETTER KA
         // U+0916: "ख" DEVANAGARI LETTER KHA
         // U+0917: "ग" DEVANAGARI LETTER GA
-        /* 42 */ "\u0915\u0916\u0917",
-        /* 43~ */
+        /* 45 */ "\u0915\u0916\u0917",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20B9: "₹" INDIAN RUPEE SIGN
-        /* 48 */ "\u20B9",
-        /* 49~ */
+        /* 51 */ "\u20B9",
+        /* 52~ */
         null, null, null, null, null, null, null, null, null, null, null,
-        /* ~59 */
+        /* ~62 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
-        /* 60 */ "\u0967",
+        /* 63 */ "\u0967",
         // U+0968: "२" DEVANAGARI DIGIT TWO
-        /* 61 */ "\u0968",
+        /* 64 */ "\u0968",
         // U+0969: "३" DEVANAGARI DIGIT THREE
-        /* 62 */ "\u0969",
+        /* 65 */ "\u0969",
         // U+096A: "४" DEVANAGARI DIGIT FOUR
-        /* 63 */ "\u096A",
+        /* 66 */ "\u096A",
         // U+096B: "५" DEVANAGARI DIGIT FIVE
-        /* 64 */ "\u096B",
+        /* 67 */ "\u096B",
         // U+096C: "६" DEVANAGARI DIGIT SIX
-        /* 65 */ "\u096C",
+        /* 68 */ "\u096C",
         // U+096D: "७" DEVANAGARI DIGIT SEVEN
-        /* 66 */ "\u096D",
+        /* 69 */ "\u096D",
         // U+096E: "८" DEVANAGARI DIGIT EIGHT
-        /* 67 */ "\u096E",
+        /* 70 */ "\u096E",
         // U+096F: "९" DEVANAGARI DIGIT NINE
-        /* 68 */ "\u096F",
+        /* 71 */ "\u096F",
         // U+0966: "०" DEVANAGARI DIGIT ZERO
-        /* 69 */ "\u0966",
+        /* 72 */ "\u0966",
         // Label for "switch to symbols" key.
-        /* 70 */ "?\u0967\u0968\u0969",
+        /* 73 */ "?\u0967\u0968\u0969",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u0967\u0968\u0969",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
-        /* 81 */ "0",
+        /* 74 */ "\u0967\u0968\u0969",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
+        /* 84 */ "0",
     };
 
     /* Language hr: Croatian */
@@ -1649,11 +1700,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language hu: Hungarian */
@@ -1702,12 +1754,12 @@
         /* 5~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language is: Icelandic */
@@ -1773,10 +1825,10 @@
         /* 22 */ "\u00FE",
         /* 23~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language it: Italian */
@@ -1829,13 +1881,13 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+05D0: "א" HEBREW LETTER ALEF
         // U+05D1: "ב" HEBREW LETTER BET
         // U+05D2: "ג" HEBREW LETTER GIMEL
-        /* 42 */ "\u05D0\u05D1\u05D2",
+        /* 45 */ "\u05D0\u05D1\u05D2",
         // The following characters don't need BIDI mirroring.
         // U+2018: "‘" LEFT SINGLE QUOTATION MARK
         // U+2019: "’" RIGHT SINGLE QUOTATION MARK
@@ -1843,31 +1895,31 @@
         // U+201C: "“" LEFT DOUBLE QUOTATION MARK
         // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
         // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        /* 43 */ "\u2018,\u2019,\u201A",
-        /* 44 */ "\u201C,\u201D,\u201E",
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 46 */ "\u2018,\u2019,\u201A",
+        /* 47 */ "\u201C,\u201D,\u201E",
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null, null,
-        /* ~50 */
+        /* ~53 */
         // U+2605: "★" BLACK STAR
-        /* 51 */ "\u2605",
-        /* 52 */ null,
+        /* 54 */ "\u2605",
+        /* 55 */ null,
         // U+00B1: "±" PLUS-MINUS SIGN
         // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
-        /* 53 */ "\u00B1,\uFB29",
+        /* 56 */ "\u00B1,\uFB29",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 54 */ "!fixedColumnOrder!3,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
     };
 
     /* Language ka: Georgian */
@@ -1875,15 +1927,63 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+10D0: "ა" GEORGIAN LETTER AN
         // U+10D1: "ბ" GEORGIAN LETTER BAN
         // U+10D2: "გ" GEORGIAN LETTER GAN
-        /* 42 */ "\u10D0\u10D1\u10D2",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u10D0\u10D1\u10D2",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+    };
+
+    /* Language kk: Kazakh */
+    private static final String[] LANGUAGE_kk = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null,
+        /* ~24 */
+        // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+        /* 25 */ "\u0449",
+        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+        /* 26 */ "\u044A",
+        // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+        /* 27 */ "\u044B",
+        // U+044D: "э" CYRILLIC SMALL LETTER E
+        /* 28 */ "\u044D",
+        // U+0438: "и" CYRILLIC SMALL LETTER I
+        /* 29 */ "\u0438",
+        // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+        // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+        /* 30 */ "\u04AF,\u04B1",
+        // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER
+        /* 31 */ "\u049B",
+        // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
+        /* 32 */ "\u04A3",
+        // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
+        /* 33 */ "\u0493",
+        // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+        /* 34 */ "\u0456",
+        // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA
+        /* 35 */ "\u04D9",
+        // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
+        /* 36 */ "\u04E9",
+        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+        /* 37 */ "\u044A",
+        // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
+        /* 38 */ "\u04BB",
+        /* 39~ */
+        null, null, null, null,
+        /* ~42 */
+        // U+0451: "ё" CYRILLIC SMALL LETTER IO
+        /* 43 */ "\u0451",
+        /* 44 */ null,
+        // Label for "switch to alphabetic" key.
+        // U+0410: "А" CYRILLIC CAPITAL LETTER A
+        // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
+        // U+0412: "В" CYRILLIC CAPITAL LETTER VE
+        /* 45 */ "\u0410\u0411\u0412",
     };
 
     /* Language ky: Kirghiz */
@@ -1904,25 +2004,27 @@
         /* 29 */ "\u0438",
         // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
         /* 30 */ "\u04AF",
+        /* 31 */ null,
         // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
-        /* 31 */ "\u04A3",
-        /* 32 */ null,
-        /* 33 */ null,
+        /* 32 */ "\u04A3",
+        /* 33~ */
+        null, null, null,
+        /* ~35 */
         // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
-        /* 34 */ "\u04E9",
+        /* 36 */ "\u04E9",
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
+        /* 45 */ "\u0410\u0411\u0412",
     };
 
     /* Language lt: Lithuanian */
@@ -2015,10 +2117,10 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language lv: Latvian */
@@ -2110,10 +2212,10 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language mk: Macedonian */
@@ -2121,27 +2223,27 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~35 */
+        null, null, null, null, null, null, null, null, null,
+        /* ~38 */
         // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
-        /* 36 */ "\u0455",
+        /* 39 */ "\u0455",
         // U+045C: "ќ" CYRILLIC SMALL LETTER KJE
-        /* 37 */ "\u045C",
+        /* 40 */ "\u045C",
         // U+0437: "з" CYRILLIC SMALL LETTER ZE
-        /* 38 */ "\u0437",
+        /* 41 */ "\u0437",
         // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE
-        /* 39 */ "\u0453",
+        /* 42 */ "\u0453",
         // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
-        /* 40 */ "\u0450",
+        /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
-        /* 41 */ "\u045D",
+        /* 44 */ "\u045D",
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language mn: Mongolian */
@@ -2149,18 +2251,18 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43~ */
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20AE: "₮" TUGRIK SIGN
-        /* 48 */ "\u20AE",
+        /* 51 */ "\u20AE",
     };
 
     /* Language nb: Norwegian Bokmål */
@@ -2210,10 +2312,10 @@
         /* 24 */ "\u00E4",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language nl: Dutch */
@@ -2268,10 +2370,10 @@
         /* 9~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language pl: Polish */
@@ -2328,10 +2430,11 @@
         /* 14 */ "\u0142",
         /* 15~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language pt: Portuguese */
@@ -2434,10 +2537,10 @@
         /* 12~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language ru: Russian */
@@ -2457,23 +2560,23 @@
         // U+0438: "и" CYRILLIC SMALL LETTER I
         /* 29 */ "\u0438",
         /* 30~ */
-        null, null, null, null, null,
-        /* ~34 */
+        null, null, null, null, null, null, null,
+        /* ~36 */
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language sk: Slovak */
@@ -2566,12 +2669,12 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sl: Slovenian */
@@ -2595,11 +2698,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sr: Serbian */
@@ -2607,8 +2711,8 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~35 */
+        null, null, null, null, null, null, null, null, null,
+        /* ~38 */
         // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
         // BEGIN: More keys definitions for Serbian (Latin)
         // U+0161: "š" LATIN SMALL LETTER S WITH CARON
@@ -2628,27 +2732,27 @@
         // END: More keys definitions for Serbian (Latin)
         // BEGIN: More keys definitions for Serbian (Cyrillic)
         // U+0437: "з" CYRILLIC SMALL LETTER ZE
-        /* 36 */ "\u0437",
+        /* 39 */ "\u0437",
         // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE
-        /* 37 */ "\u045B",
+        /* 40 */ "\u045B",
         // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
-        /* 38 */ "\u0455",
+        /* 41 */ "\u0455",
         // U+0452: "ђ" CYRILLIC SMALL LETTER DJE
-        /* 39 */ "\u0452",
+        /* 42 */ "\u0452",
         // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
-        /* 40 */ "\u0450",
+        /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
-        /* 41 */ "\u045D",
+        /* 44 */ "\u045D",
         // END: More keys definitions for Serbian (Cyrillic)
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sv: Swedish */
@@ -2693,10 +2797,10 @@
         /* 24 */ "\u00E6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null,
-        /* ~44 */
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null,
+        /* ~47 */
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sw: Swahili */
@@ -2755,18 +2859,18 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0E01: "ก" THAI CHARACTER KO KAI
         // U+0E02: "ข" THAI CHARACTER KHO KHAI
         // U+0E04: "ค" THAI CHARACTER KHO KHWAI
-        /* 42 */ "\u0E01\u0E02\u0E04",
-        /* 43~ */
+        /* 45 */ "\u0E01\u0E02\u0E04",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
-        /* 48 */ "\u0E3F",
+        /* 51 */ "\u0E3F",
     };
 
     /* Language tl: Tagalog */
@@ -2884,30 +2988,32 @@
         /* 28 */ "\u0454",
         // U+0438: "и" CYRILLIC SMALL LETTER I
         /* 29 */ "\u0438",
-        /* 30 */ null,
-        /* 31 */ null,
+        /* 30~ */
+        null, null, null,
+        /* ~32 */
         // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
-        /* 32 */ "\u0491",
+        /* 33 */ "\u0491",
         // U+0457: "ї" CYRILLIC SMALL LETTER YI
-        /* 33 */ "\u0457",
-        /* 34 */ null,
+        /* 34 */ "\u0457",
+        /* 35 */ null,
+        /* 36 */ null,
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null, null, null,
-        /* ~41 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45~ */
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48~ */
         null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20B4: "₴" HRYVNIA SIGN
-        /* 48 */ "\u20B4",
+        /* 51 */ "\u20B4",
     };
 
     /* Language vi: Vietnamese */
@@ -2992,10 +3098,10 @@
         /* 10~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~47 */
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~50 */
         // U+20AB: "₫" DONG SIGN
-        /* 48 */ "\u20AB",
+        /* 51 */ "\u20AB",
     };
 
     /* Language zu: Zulu */
@@ -3172,6 +3278,7 @@
         "DEFAULT", LANGUAGE_DEFAULT, /* default */
         "af", LANGUAGE_af, /* Afrikaans */
         "ar", LANGUAGE_ar, /* Arabic */
+        "az", LANGUAGE_az, /* Azerbaijani */
         "be", LANGUAGE_be, /* Belarusian */
         "bg", LANGUAGE_bg, /* Bulgarian */
         "ca", LANGUAGE_ca, /* Catalan */
@@ -3193,6 +3300,7 @@
         "it", LANGUAGE_it, /* Italian */
         "iw", LANGUAGE_iw, /* Hebrew */
         "ka", LANGUAGE_ka, /* Georgian */
+        "kk", LANGUAGE_kk, /* Kazakh */
         "ky", LANGUAGE_ky, /* Kirghiz */
         "lt", LANGUAGE_lt, /* Lithuanian */
         "lv", LANGUAGE_lv, /* Latvian */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
index db154a3..7c2e3e1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java
@@ -17,7 +17,7 @@
 package com.android.inputmethod.keyboard.internal;
 
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
new file mode 100644
index 0000000..c1f3749
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/MatrixUtils.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Utilities for matrix operations. Don't instantiate objects inside this class to prevent
+ * unexpected performance regressions.
+ */
+@UsedForTesting
+public class MatrixUtils {
+    private static final String TAG = MatrixUtils.class.getSimpleName();
+    public static class MatrixOperationFailedException extends Exception {
+        private static final long serialVersionUID = 4384485606788583829L;
+
+        public MatrixOperationFailedException(String msg) {
+            super(msg);
+            Log.d(TAG, msg);
+        }
+    }
+
+    /**
+     * A utility function to inverse matrix.
+     * Find a pivot and swap the row of squareMatrix0 and squareMatrix1
+     */
+    private static void findPivotAndSwapRow(final int row, final float[][] squareMatrix0,
+            final float[][] squareMatrix1, final int size) {
+        int ip = row;
+        float pivot = Math.abs(squareMatrix0[row][row]);
+        for (int i = row + 1; i < size; ++i) {
+            if (pivot < Math.abs(squareMatrix0[i][row])) {
+                ip = i;
+                pivot = Math.abs(squareMatrix0[i][row]);
+            }
+        }
+        if (ip != row) {
+            for (int j = 0; j < size; ++j) {
+                final float temp0 = squareMatrix0[ip][j];
+                squareMatrix0[ip][j] = squareMatrix0[row][j];
+                squareMatrix0[row][j] = temp0;
+                final float temp1 = squareMatrix1[ip][j];
+                squareMatrix1[ip][j] = squareMatrix1[row][j];
+                squareMatrix1[row][j] = temp1;
+            }
+        }
+    }
+
+    /**
+     * A utility function to inverse matrix. This function calculates answer for each row by
+     * sweeping method of Gauss Jordan elimination
+     */
+    private static void sweep(final int row, final float[][] squareMatrix0,
+            final float[][] squareMatrix1, final int size) throws MatrixOperationFailedException {
+        final float pivot = squareMatrix0[row][row];
+        if (pivot == 0) {
+            throw new MatrixOperationFailedException("Inverse failed. Invalid pivot");
+        }
+        for (int j = 0; j < size; ++j) {
+            squareMatrix0[row][j] /= pivot;
+            squareMatrix1[row][j] /= pivot;
+        }
+        for (int i = 0; i < size; i++) {
+            final float sweepTargetValue = squareMatrix0[i][row];
+            if (i != row) {
+                for (int j = row; j < size; ++j) {
+                    squareMatrix0[i][j] -= sweepTargetValue * squareMatrix0[row][j];
+                }
+                for (int j = 0; j < size; ++j) {
+                    squareMatrix1[i][j] -= sweepTargetValue * squareMatrix1[row][j];
+                }
+            }
+        }
+    }
+
+    /**
+     * A function to inverse matrix.
+     * The inverse matrix of squareMatrix will be output to inverseMatrix. Please notice that
+     * the value of squareMatrix is modified in this function and can't be resuable.
+     */
+    @UsedForTesting
+    public static void inverse(final float[][] squareMatrix,
+            final float[][] inverseMatrix) throws MatrixOperationFailedException {
+        final int size = squareMatrix.length;
+        if (squareMatrix[0].length != size || inverseMatrix.length != size
+                || inverseMatrix[0].length != size) {
+            throw new MatrixOperationFailedException(
+                    "--- invalid length. column should be 2 times larger than row.");
+        }
+        for (int i = 0; i < size; ++i) {
+            Arrays.fill(inverseMatrix[i], 0.0f);
+            inverseMatrix[i][i] = 1.0f;
+        }
+        for (int i = 0; i < size; ++i) {
+            findPivotAndSwapRow(i, squareMatrix, inverseMatrix, size);
+            sweep(i, squareMatrix, inverseMatrix, size);
+        }
+    }
+
+    /**
+     * A matrix operation to multiply m0 and m1.
+     */
+    @UsedForTesting
+    public static void multiply(final float[][] m0, final float[][] m1,
+            final float[][] retval) throws MatrixOperationFailedException {
+        if (m0[0].length != m1.length) {
+            throw new MatrixOperationFailedException(
+                    "--- invalid length for multiply " + m0[0].length + ", " + m1.length);
+        }
+        final int m0h = m0.length;
+        final int m0w = m0[0].length;
+        final int m1w = m1[0].length;
+        if (retval.length != m0h || retval[0].length != m1w) {
+            throw new MatrixOperationFailedException(
+                    "--- invalid length of retval " + retval.length + ", " + retval[0].length);
+        }
+
+        for (int i = 0; i < m0h; i++) {
+            Arrays.fill(retval[i], 0);
+            for (int j = 0; j < m1w; j++) {
+                for (int k = 0; k < m0w; k++) {
+                    retval[i][j] += m0[i][k] * m1[k][j];
+                }
+            }
+        }
+    }
+
+    /**
+     * A utility function to dump the specified matrix in a readable way
+     */
+    @UsedForTesting
+    public static void dump(final String title, final float[][] a) {
+        final int column = a[0].length;
+        final int row = a.length;
+        Log.d(TAG, "Dump matrix: " + title);
+        Log.d(TAG, "/*---------------------");
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < row; ++i) {
+            sb.setLength(0);
+            for (int j = 0; j < column; ++j) {
+                sb.append(String.format("%4f", a[i][j])).append(' ');
+            }
+            Log.d(TAG, sb.toString());
+        }
+        Log.d(TAG, "---------------------*/");
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
index b38d79f..110936f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java
@@ -19,7 +19,7 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Locale;
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
new file mode 100644
index 0000000..a0935b9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/NonDistinctMultitouchHelper.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.PointerTracker;
+import com.android.inputmethod.keyboard.PointerTracker.KeyEventHandler;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
+
+public final class NonDistinctMultitouchHelper {
+    private static final String TAG = NonDistinctMultitouchHelper.class.getSimpleName();
+
+    private int mOldPointerCount = 1;
+    private Key mOldKey;
+    private int[] mLastCoords = CoordinateUtils.newInstance();
+
+    public void processMotionEvent(final MotionEvent me, final KeyEventHandler keyEventHandler) {
+        final int pointerCount = me.getPointerCount();
+        final int oldPointerCount = mOldPointerCount;
+        mOldPointerCount = pointerCount;
+        // Ignore continuous multi-touch events because we can't trust the coordinates
+        // in multi-touch events.
+        if (pointerCount > 1 && oldPointerCount > 1) {
+            return;
+        }
+
+        // Use only main (id=0) pointer tracker.
+        final PointerTracker mainTracker = PointerTracker.getPointerTracker(0, keyEventHandler);
+        final int action = me.getActionMasked();
+        final int index = me.getActionIndex();
+        final long eventTime = me.getEventTime();
+        final long downTime = me.getDownTime();
+
+        // In single-touch.
+        if (oldPointerCount == 1 && pointerCount == 1) {
+            if (me.getPointerId(index) == mainTracker.mPointerId) {
+                mainTracker.processMotionEvent(me, keyEventHandler);
+                return;
+            }
+            // Inject a copied event.
+            injectMotionEvent(action, me.getX(index), me.getY(index), downTime, eventTime,
+                    mainTracker, keyEventHandler);
+            return;
+        }
+
+        // Single-touch to multi-touch transition.
+        if (oldPointerCount == 1 && pointerCount == 2) {
+            // Send an up event for the last pointer, be cause we can't trust the coordinates of
+            // this multi-touch event.
+            mainTracker.getLastCoordinates(mLastCoords);
+            final int x = CoordinateUtils.x(mLastCoords);
+            final int y = CoordinateUtils.y(mLastCoords);
+            mOldKey = mainTracker.getKeyOn(x, y);
+            // Inject an artifact up event for the old key.
+            injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+                    mainTracker, keyEventHandler);
+            return;
+        }
+
+        // Multi-touch to single-touch transition.
+        if (oldPointerCount == 2 && pointerCount == 1) {
+            // Send a down event for the latest pointer if the key is different from the previous
+            // key.
+            final int x = (int)me.getX(index);
+            final int y = (int)me.getY(index);
+            final Key newKey = mainTracker.getKeyOn(x, y);
+            if (mOldKey != newKey) {
+                // Inject an artifact down event for the new key.
+                // An artifact up event for the new key will usually be injected as a single-touch.
+                injectMotionEvent(MotionEvent.ACTION_DOWN, x, y, downTime, eventTime,
+                        mainTracker, keyEventHandler);
+                if (action == MotionEvent.ACTION_UP) {
+                    // Inject an artifact up event for the new key also.
+                    injectMotionEvent(MotionEvent.ACTION_UP, x, y, downTime, eventTime,
+                            mainTracker, keyEventHandler);
+                }
+            }
+            return;
+        }
+
+        Log.w(TAG, "Unknown touch panel behavior: pointer count is "
+                + pointerCount + " (previously " + oldPointerCount + ")");
+    }
+
+    private static void injectMotionEvent(final int action, final float x, final float y,
+            final long downTime, final long eventTime, final PointerTracker tracker,
+            final KeyEventHandler handler) {
+        final MotionEvent me = MotionEvent.obtain(
+                downTime, eventTime, action, x, y, 0 /* metaState */);
+        try {
+            tracker.processMotionEvent(me, handler);
+        } finally {
+            me.recycle();
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
index 31ef3cd..7ee45e8 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java
@@ -18,7 +18,7 @@
 
 import android.util.Log;
 
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 
@@ -207,7 +207,7 @@
         }
     }
 
-    public void cancelAllPointerTracker() {
+    public void cancelAllPointerTrackers() {
         synchronized (mExpandableArrayOfActivePointers) {
             if (DEBUG) {
                 Log.d(TAG, "cancelAllPointerTracker: " + this);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
index 2376110..4c8607d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java
@@ -24,8 +24,8 @@
 import android.util.AttributeSet;
 import android.widget.RelativeLayout;
 
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.CoordinateUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 
 import java.util.ArrayList;
 
@@ -37,7 +37,10 @@
     public PreviewPlacerView(final Context context, final AttributeSet attrs) {
         super(context, attrs);
         setWillNotDraw(false);
+    }
 
+    public void setHardwareAcceleratedDrawingEnabled(final boolean enabled) {
+        if (!enabled) return;
         final Paint layerPaint = new Paint();
         layerPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));
         setLayerType(LAYER_TYPE_HARDWARE, layerPaint);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
index 2eefd6a..211ef5f 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java
@@ -37,16 +37,18 @@
      * @param p2x the x-coordinate of the end point.
      * @param p2y the y-coordinate of the end point.
      * @param r2 the radius at the end point
-     * @return the path of rounded line
+     * @return an instance of {@link Path} that holds the result rounded line, or an instance of
+     * {@link Path} that holds an empty path if the start and end points are equal.
      */
     public Path makePath(final float p1x, final float p1y, final float r1,
             final float p2x, final float p2y, final float r2) {
+        mPath.rewind();
         final double dx = p2x - p1x;
         final double dy = p2y - p1y;
         // Distance of the points.
         final double l = Math.hypot(dx, dy);
         if (Double.compare(0.0d, l) == 0) {
-            return null;
+            return mPath; // Return an empty path
         }
         // Angle of the line p1-p2
         final double a = Math.atan2(dy, dx);
@@ -86,7 +88,6 @@
         mArc2.set(p2x, p2y, p2x, p2y);
         mArc2.inset(-r2, -r2);
 
-        mPath.rewind();
         // Trail cap at P1.
         mPath.moveTo(p1x, p1y);
         mPath.arcTo(mArc1, angle, a1);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
index 33dbbaf..2787ebf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java
@@ -23,8 +23,8 @@
 import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.CoordinateUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.CoordinateUtils;
 
 /**
  * Draw rubber band preview graphics during sliding key input.
@@ -32,7 +32,7 @@
 public final class SlidingKeyInputPreview extends AbstractDrawingPreview {
     private final float mPreviewBodyRadius;
 
-    private boolean mShowSlidingKeyInputPreview;
+    private boolean mShowsSlidingKeyInputPreview;
     private final int[] mPreviewFrom = CoordinateUtils.newInstance();
     private final int[] mPreviewTo = CoordinateUtils.newInstance();
 
@@ -62,7 +62,7 @@
     }
 
     public void dismissSlidingKeyInputPreview() {
-        mShowSlidingKeyInputPreview = false;
+        mShowsSlidingKeyInputPreview = false;
         getDrawingView().invalidate();
     }
 
@@ -72,7 +72,7 @@
      */
     @Override
     public void drawPreview(final Canvas canvas) {
-        if (!isPreviewEnabled() || !mShowSlidingKeyInputPreview) {
+        if (!isPreviewEnabled() || !mShowsSlidingKeyInputPreview) {
             return;
         }
 
@@ -90,13 +90,9 @@
      */
     @Override
     public void setPreviewPosition(final PointerTracker tracker) {
-        if (!tracker.isInSlidingKeyInputFromModifier()) {
-            mShowSlidingKeyInputPreview = false;
-            return;
-        }
         tracker.getDownCoordinates(mPreviewFrom);
         tracker.getLastCoordinates(mPreviewTo);
-        mShowSlidingKeyInputPreview = true;
+        mShowsSlidingKeyInputPreview = true;
         getDrawingView().invalidate();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
new file mode 100644
index 0000000..10847f6
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/SmoothingUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.keyboard.internal.MatrixUtils.MatrixOperationFailedException;
+
+import android.util.Log;
+
+import java.util.Arrays;
+
+/**
+ * Utilities to smooth coordinates. Currently, we calculate 3d least squares formula by using
+ * Lagrangian smoothing
+ */
+@UsedForTesting
+public class SmoothingUtils {
+    private static final String TAG = SmoothingUtils.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private SmoothingUtils() {
+        // not allowed to instantiate publicly
+    }
+
+    /**
+     * Find a most likely 3d least squares formula for specified coordinates.
+     * "retval" should be a 1x4 size matrix.
+     */
+    @UsedForTesting
+    public static void get3DParameters(final float[] xs, final float[] ys,
+            final float[][] retval) throws MatrixOperationFailedException {
+        final int COEFF_COUNT = 4; // Coefficient count for 3d smoothing
+        if (retval.length != COEFF_COUNT || retval[0].length != 1) {
+            Log.d(TAG, "--- invalid length of 3d retval " + retval.length + ", "
+                    + retval[0].length);
+            return;
+        }
+        final int N = xs.length;
+        // TODO: Never isntantiate the matrix
+        final float[][] m0 = new float[COEFF_COUNT][COEFF_COUNT];
+        final float[][] m0Inv = new float[COEFF_COUNT][COEFF_COUNT];
+        final float[][] m1 = new float[COEFF_COUNT][N];
+        final float[][] m2 = new float[N][1];
+
+        // m0
+        for (int i = 0; i < COEFF_COUNT; ++i) {
+            Arrays.fill(m0[i], 0);
+            for (int j = 0; j < COEFF_COUNT; ++j) {
+                final int pow = i + j;
+                for (int k = 0; k < N; ++k) {
+                    m0[i][j] += (float) Math.pow(xs[k], pow);
+                }
+            }
+        }
+        // m0Inv
+        MatrixUtils.inverse(m0, m0Inv);
+        if (DEBUG) {
+            MatrixUtils.dump("m0-1", m0Inv);
+        }
+
+        // m1
+        for (int i = 0; i < COEFF_COUNT; ++i) {
+            for (int j = 0; j < N; ++j) {
+                m1[i][j] = (i == 0) ? 1.0f : m1[i - 1][j] * xs[j];
+            }
+        }
+
+        // m2
+        for (int i = 0; i < N; ++i) {
+            m2[i][0] = ys[i];
+        }
+
+        final float[][] m0Invxm1 = new float[COEFF_COUNT][N];
+        if (DEBUG) {
+            MatrixUtils.dump("a0", m0Inv);
+            MatrixUtils.dump("a1", m1);
+        }
+        MatrixUtils.multiply(m0Inv, m1, m0Invxm1);
+        if (DEBUG) {
+            MatrixUtils.dump("a2", m0Invxm1);
+            MatrixUtils.dump("a3", m2);
+        }
+        MatrixUtils.multiply(m0Invxm1, m2, retval);
+        if (DEBUG) {
+            MatrixUtils.dump("result", retval);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
deleted file mode 100644
index d6b1cc6..0000000
--- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard.internal;
-
-import android.content.Context;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.ResourceUtils;
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.research.ResearchLogger;
-
-public final class TouchScreenRegulator {
-    private static final String TAG = TouchScreenRegulator.class.getSimpleName();
-    private static boolean DEBUG_MODE = LatinImeLogger.sDBG;
-
-    public interface ProcessMotionEvent {
-        public boolean processMotionEvent(MotionEvent me);
-    }
-
-    private final ProcessMotionEvent mView;
-    private final boolean mNeedsSuddenJumpingHack;
-
-    /** Whether we've started dropping move events because we found a big jump */
-    private boolean mDroppingEvents;
-    /**
-     * Whether multi-touch disambiguation needs to be disabled if a real multi-touch event has
-     * occured
-     */
-    private boolean mDisableDisambiguation;
-    /** The distance threshold at which we start treating the touch session as a multi-touch */
-    private int mJumpThresholdSquare = Integer.MAX_VALUE;
-    private int mLastX;
-    private int mLastY;
-    // One-seventh of the keyboard width seems like a reasonable threshold
-    private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f;
-
-    public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) {
-        mView = view;
-        mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue(
-                context.getResources(), R.array.sudden_jumping_touch_event_device_list));
-    }
-
-    public void setKeyboardGeometry(final int keyboardWidth) {
-        final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH;
-        mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold);
-    }
-
-    /**
-     * This function checks to see if we need to handle any sudden jumps in the pointer location
-     * that could be due to a multi-touch being treated as a move by the firmware or hardware.
-     * Once a sudden jump is detected, all subsequent move events are discarded
-     * until an UP is received.<P>
-     * When a sudden jump is detected, an UP event is simulated at the last position and when
-     * the sudden moves subside, a DOWN event is simulated for the second key.
-     * @param me the motion event
-     * @return true if the event was consumed, so that it doesn't continue to be handled by
-     * {@link MainKeyboardView}.
-     */
-    private boolean handleSuddenJumping(final MotionEvent me) {
-        if (!mNeedsSuddenJumpingHack)
-            return false;
-        final int action = me.getAction();
-        final int x = (int) me.getX();
-        final int y = (int) me.getY();
-        boolean result = false;
-
-        // Real multi-touch event? Stop looking for sudden jumps
-        if (me.getPointerCount() > 1) {
-            mDisableDisambiguation = true;
-        }
-        if (mDisableDisambiguation) {
-            // If UP, reset the multi-touch flag
-            if (action == MotionEvent.ACTION_UP) mDisableDisambiguation = false;
-            return false;
-        }
-
-        switch (action) {
-        case MotionEvent.ACTION_DOWN:
-            // Reset the "session"
-            mDroppingEvents = false;
-            mDisableDisambiguation = false;
-            break;
-        case MotionEvent.ACTION_MOVE:
-            // Is this a big jump?
-            final int distanceSquare = (mLastX - x) * (mLastX - x) + (mLastY - y) * (mLastY - y);
-            // Check the distance.
-            if (distanceSquare > mJumpThresholdSquare) {
-                // If we're not yet dropping events, start dropping and send an UP event
-                if (!mDroppingEvents) {
-                    mDroppingEvents = true;
-                    // Send an up event
-                    MotionEvent translated = MotionEvent.obtain(
-                            me.getEventTime(), me.getEventTime(),
-                            MotionEvent.ACTION_UP,
-                            mLastX, mLastY, me.getMetaState());
-                    mView.processMotionEvent(translated);
-                    translated.recycle();
-                }
-                result = true;
-            } else if (mDroppingEvents) {
-                // If moves are small and we're already dropping events, continue dropping
-                result = true;
-            }
-            break;
-        case MotionEvent.ACTION_UP:
-            if (mDroppingEvents) {
-                // Send a down event first, as we dropped a bunch of sudden jumps and assume that
-                // the user is releasing the touch on the second key.
-                MotionEvent translated = MotionEvent.obtain(me.getEventTime(), me.getEventTime(),
-                        MotionEvent.ACTION_DOWN,
-                        x, y, me.getMetaState());
-                mView.processMotionEvent(translated);
-                translated.recycle();
-                mDroppingEvents = false;
-                // Let the up event get processed as well, result = false
-            }
-            break;
-        }
-        // Track the previous coordinate
-        mLastX = x;
-        mLastY = y;
-        return result;
-    }
-
-    public boolean onTouchEvent(final MotionEvent me) {
-        // If there was a sudden jump, return without processing the actual motion event.
-        if (handleSuddenJumping(me)) {
-            if (DEBUG_MODE)
-                Log.w(TAG, "onTouchEvent: ignore sudden jump " + me);
-            if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.suddenJumpingTouchEventHandler_onTouchEvent(me);
-            }
-            return true;
-        }
-        return mView.processMotionEvent(me);
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
new file mode 100644
index 0000000..ebbcedc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.content.Context;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
+abstract public class AbstractDictionaryWriter extends Dictionary {
+    /** Used for Log actions from this class */
+    private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
+
+    private final Context mContext;
+
+    public AbstractDictionaryWriter(final Context context, final String dictType) {
+        super(dictType);
+        mContext = context;
+    }
+
+    abstract public void clear();
+
+    abstract public void addUnigramWord(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord);
+
+    abstract public void addBigramWords(final String word0, final String word1,
+            final int frequency, final boolean isValid);
+
+    abstract public void removeBigramWords(final String word0, final String word1);
+
+    abstract protected void writeBinaryDictionary(final FileOutputStream out)
+            throws IOException, UnsupportedFormatException;
+
+    public void write(final String fileName) {
+        final String tempFileName = fileName + ".temp";
+        final File file = new File(mContext.getFilesDir(), fileName);
+        final File tempFile = new File(mContext.getFilesDir(), tempFileName);
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(tempFile);
+            writeBinaryDictionary(out);
+            out.flush();
+            out.close();
+            tempFile.renameTo(file);
+        } catch (IOException e) {
+            Log.e(TAG, "IO exception while writing file", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AssetFileAddress.java b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
index 47c750f..8751925 100644
--- a/java/src/com/android/inputmethod/latin/AssetFileAddress.java
+++ b/java/src/com/android/inputmethod/latin/AssetFileAddress.java
@@ -24,7 +24,7 @@
  * the package file. Open it correctly thus requires the name of the package it is in, but
  * also the offset in the file and the length of this data. This class encapsulates these three.
  */
-final class AssetFileAddress {
+public final class AssetFileAddress {
     public final String mFilename;
     public final long mOffset;
     public final long mLength;
diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
index 986b1a1..42c5794 100644
--- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
+++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.settings.SettingsValues;
+
 import android.content.Context;
 import android.media.AudioManager;
 import android.os.Vibrator;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 4fc1919..d181bf6 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -21,6 +21,11 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.settings.AdditionalFeaturesSettingUtils;
+import com.android.inputmethod.latin.settings.NativeSuggestOptions;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,7 +38,7 @@
     private static final String TAG = BinaryDictionary.class.getSimpleName();
 
     // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
-    private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+    private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
     // Must be equal to MAX_RESULTS in native/jni/src/defines.h
     private static final int MAX_RESULTS = 18;
 
@@ -45,7 +50,7 @@
     private final int[] mOutputScores = new int[MAX_RESULTS];
     private final int[] mOutputTypes = new int[MAX_RESULTS];
 
-    private final boolean mUseFullEditDistance;
+    private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
 
     private final SparseArray<DicTraverseSession> mDicTraverseSessions =
             CollectionUtils.newSparseArray();
@@ -74,35 +79,42 @@
      * @param length the length of the binary data.
      * @param useFullEditDistance whether to use the full edit distance in suggestions
      * @param dictType the dictionary type, as a human-readable string
+     * @param isUpdatable whether to open the dictionary file in writable mode.
      */
     public BinaryDictionary(final String filename, final long offset, final long length,
-            final boolean useFullEditDistance, final Locale locale, final String dictType) {
+            final boolean useFullEditDistance, final Locale locale, final String dictType,
+            final boolean isUpdatable) {
         super(dictType);
         mLocale = locale;
-        mUseFullEditDistance = useFullEditDistance;
-        loadDictionary(filename, offset, length);
+        mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
+        loadDictionary(filename, offset, length, isUpdatable);
     }
 
     static {
         JniUtils.loadNativeLibrary();
     }
 
-    private static native long openNative(String sourceDir, long dictOffset, long dictSize);
+    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
+            boolean isUpdatable);
     private static native void closeNative(long dict);
     private static native int getProbabilityNative(long dict, int[] word);
-    private static native boolean isValidBigramNative(long dict, int[] word1, int[] word2);
+    private static native boolean isValidBigramNative(long dict, int[] word0, int[] word1);
     private static native int getSuggestionsNative(long dict, long proximityInfo,
             long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
             int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
-            boolean isGesture, int[] prevWordCodePointArray, boolean useFullEditDistance,
+            int[] suggestOptions, int[] prevWordCodePointArray,
             int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
     private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
     private static native int editDistanceNative(int[] before, int[] after);
+    private static native void addUnigramWordNative(long dict, int[] word, int probability);
+    private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
+            int probability);
+    private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
 
     // TODO: Move native dict into session
     private final void loadDictionary(final String path, final long startOffset,
-            final long length) {
-        mNativeDict = openNative(path, startOffset, length);
+            final long length, final boolean isUpdatable) {
+        mNativeDict = openNative(path, startOffset, length, isUpdatable);
     }
 
     @Override
@@ -135,12 +147,15 @@
 
         final InputPointers ips = composer.getInputPointers();
         final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
+        mNativeSuggestOptions.setIsGesture(isGesture);
+        mNativeSuggestOptions.setAdditionalFeaturesOptions(
+                AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions());
         // proximityInfo and/or prevWordForBigrams may not be null.
         final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
                 getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
                 ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
-                inputSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray,
-                mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices,
+                inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
+                prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
                 mOutputTypes);
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         for (int j = 0; j < count; ++j) {
@@ -202,11 +217,40 @@
 
     // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
     // calls when checking for changes in an entire dictionary.
-    public boolean isValidBigram(final String word1, final String word2) {
-        if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false;
+    public boolean isValidBigram(final String word0, final String word1) {
+        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return false;
+        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
         final int[] codePoints1 = StringUtils.toCodePointArray(word1);
-        final int[] codePoints2 = StringUtils.toCodePointArray(word2);
-        return isValidBigramNative(mNativeDict, codePoints1, codePoints2);
+        return isValidBigramNative(mNativeDict, codePoints0, codePoints1);
+    }
+
+    // Add a unigram entry to binary dictionary in native code.
+    public void addUnigramWord(final String word, final int probability) {
+        if (TextUtils.isEmpty(word)) {
+            return;
+        }
+        final int[] codePoints = StringUtils.toCodePointArray(word);
+        addUnigramWordNative(mNativeDict, codePoints, probability);
+    }
+
+    // Add a bigram entry to binary dictionary in native code.
+    public void addBigramWords(final String word0, final String word1, final int probability) {
+        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+            return;
+        }
+        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability);
+    }
+
+    // Remove a bigram entry form binary dictionary in native code.
+    public void removeBigramWords(final String word0, final String word1) {
+        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
+            return;
+        }
+        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
+        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
+        removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index a9b58de..722a829 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -28,10 +28,15 @@
 import android.util.Log;
 
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
-import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo;
+import com.android.inputmethod.latin.utils.FileTransforms;
+import com.android.inputmethod.latin.utils.MetadataFileUriGetter;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
+import java.io.Closeable;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -304,6 +309,7 @@
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
                 BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
+                Log.e(TAG, "Successfully copied file for wordlist ID " + wordlistId);
                 // Success! Close files (through the finally{} clause) and return.
                 return;
             } catch (Exception e) {
@@ -319,20 +325,12 @@
                 // Try the next method.
             } finally {
                 // Ignore exceptions while closing files.
-                try {
-                    if (null != afd) afd.close();
-                    if (null != inputStream) inputStream.close();
-                    if (null != uncompressedStream) uncompressedStream.close();
-                    if (null != decryptedStream) decryptedStream.close();
-                    if (null != bufferedInputStream) bufferedInputStream.close();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception while closing a file descriptor", e);
-                }
-                try {
-                    if (null != bufferedOutputStream) bufferedOutputStream.close();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception while closing a file", e);
-                }
+                closeAssetFileDescriptorAndReportAnyException(afd);
+                closeCloseableAndReportAnyException(inputStream);
+                closeCloseableAndReportAnyException(uncompressedStream);
+                closeCloseableAndReportAnyException(decryptedStream);
+                closeCloseableAndReportAnyException(bufferedInputStream);
+                closeCloseableAndReportAnyException(bufferedOutputStream);
             }
         }
 
@@ -352,6 +350,26 @@
         }
     }
 
+    // Ideally the two following methods should be merged, but AssetFileDescriptor does not
+    // implement Closeable although it does implement #close(), and Java does not have
+    // structural typing.
+    private static void closeAssetFileDescriptorAndReportAnyException(
+            final AssetFileDescriptor file) {
+        try {
+            if (null != file) file.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while closing a file", e);
+        }
+    }
+
+    private static void closeCloseableAndReportAnyException(final Closeable file) {
+        try {
+            if (null != file) file.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while closing a file", e);
+        }
+    }
+
     /**
      * Queries a content provider for word list data for some locale and cache the returned files
      *
@@ -363,8 +381,14 @@
      */
     public static void cacheWordListsFromContentProvider(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
-        final ContentProviderClient providerClient = context.getContentResolver().
+        final ContentProviderClient providerClient;
+        try {
+            providerClient = context.getContentResolver().
                 acquireContentProviderClient(getProviderUriBuilder("").build());
+        } catch (final SecurityException e) {
+            Log.e(TAG, "No permission to communicate with the dictionary provider", e);
+            return;
+        }
         if (null == providerClient) {
             Log.e(TAG, "Can't establish communication with the dictionary provider");
             return;
@@ -417,7 +441,6 @@
             final ContentProviderClient client, final String clientId) throws RemoteException {
         final String metadataFileUri = MetadataFileUriGetter.getMetadataUri(context);
         final String metadataAdditionalId = MetadataFileUriGetter.getMetadataAdditionalId(context);
-        if (TextUtils.isEmpty(metadataFileUri)) return;
         // Tell the content provider to reset all information about this client id
         final Uri metadataContentUri = getProviderUriBuilder(clientId)
                 .appendPath(QUERY_PATH_METADATA)
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 98eadca..fa301b5 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -16,17 +16,17 @@
 
 package com.android.inputmethod.latin;
 
-import com.android.inputmethod.latin.define.ProductionFlag;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetFileDescriptor;
 import android.util.Log;
 
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -39,7 +39,7 @@
 /**
  * Helper class to get the address of a mmap'able dictionary file.
  */
-final class BinaryDictionaryGetter {
+final public class BinaryDictionaryGetter {
 
     /**
      * Used for Log actions from this class
@@ -223,14 +223,10 @@
         }
     }
 
-    // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
-    // for this is, since those do not include whitelist entries, the new code with an old version
-    // of the dictionary would lose whitelist functionality.
+    // ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
+    // those do not include whitelist entries, the new code with an old version of the dictionary
+    // would lose whitelist functionality.
     private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
-        // Only for English - other languages didn't have a whitelist, hence this
-        // ad-hoc ## HACK ##
-        if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
-
         FileInputStream inStream = null;
         try {
             // Read the version of the file
@@ -290,17 +286,8 @@
             final Context context) {
 
         final boolean hasDefaultWordList = DictionaryFactory.isDictionaryAvailable(context, locale);
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            // We need internet access to do the following. Only do this if the package actually
-            // has the permission.
-            if (context.checkCallingOrSelfPermission(android.Manifest.permission.INTERNET)
-                    == PackageManager.PERMISSION_GRANTED) {
-                BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
-                        hasDefaultWordList);
-            }
-        }
+        BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context,
+                hasDefaultWordList);
         final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
         final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
         final DictPackSettings dictPackSettings = new DictPackSettings(context);
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 86bb255..6d67bdb 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -126,15 +126,6 @@
         }
     }
 
-    public static final class Dictionary {
-        // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
-        public static final int MAX_WORD_LENGTH = 48;
-
-        private Dictionary() {
-             // This utility class is no publicly instantiable.
-        }
-    }
-
     public static final int NOT_A_CODE = -1;
 
     public static final int NOT_A_COORDINATE = -1;
@@ -142,6 +133,10 @@
     public static final int SPELL_CHECKER_COORDINATE = -3;
     public static final int EXTERNAL_KEYBOARD_COORDINATE = -4;
 
+
+    // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
+    public static final int DICTIONARY_MAX_WORD_LENGTH = 48;
+
     public static boolean isValidCoordinate(final int coordinate) {
         // Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE},
         // and {@link SPELL_CHECKER_COORDINATE}.
@@ -149,6 +144,13 @@
     }
 
     /**
+     * Custom request code used in
+     * {@link com.android.inputmethod.keyboard.KeyboardActionListener#onCustomRequest(int)}.
+     */
+    // The code to show input method picker.
+    public static final int CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER = 1;
+
+    /**
      * Some common keys code. Must be positive.
      */
     public static final int CODE_ENTER = '\n';
@@ -172,22 +174,23 @@
 
     /**
      * Special keys code. Must be negative.
-     * These should be aligned with KeyboardCodesSet.ID_TO_NAME[],
-     * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[]
+     * These should be aligned with {@link KeyboardCodesSet#ID_TO_NAME},
+     * {@link KeyboardCodesSet#DEFAULT}, and {@link KeyboardCodesSet#RTL}.
      */
     public static final int CODE_SHIFT = -1;
-    public static final int CODE_SWITCH_ALPHA_SYMBOL = -2;
-    public static final int CODE_OUTPUT_TEXT = -3;
-    public static final int CODE_DELETE = -4;
-    public static final int CODE_SETTINGS = -5;
-    public static final int CODE_SHORTCUT = -6;
-    public static final int CODE_ACTION_NEXT = -7;
-    public static final int CODE_ACTION_PREVIOUS = -8;
-    public static final int CODE_LANGUAGE_SWITCH = -9;
-    public static final int CODE_RESEARCH = -10;
-    public static final int CODE_SHIFT_ENTER = -11;
+    public static final int CODE_CAPSLOCK = -2;
+    public static final int CODE_SWITCH_ALPHA_SYMBOL = -3;
+    public static final int CODE_OUTPUT_TEXT = -4;
+    public static final int CODE_DELETE = -5;
+    public static final int CODE_SETTINGS = -6;
+    public static final int CODE_SHORTCUT = -7;
+    public static final int CODE_ACTION_NEXT = -8;
+    public static final int CODE_ACTION_PREVIOUS = -9;
+    public static final int CODE_LANGUAGE_SWITCH = -10;
+    public static final int CODE_RESEARCH = -11;
+    public static final int CODE_SHIFT_ENTER = -12;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -12;
+    public static final int CODE_UNSPECIFIED = -13;
 
     public static boolean isLetterCode(final int code) {
         return code >= CODE_SPACE;
@@ -196,6 +199,7 @@
     public static String printableCode(final int code) {
         switch (code) {
         case CODE_SHIFT: return "shift";
+        case CODE_CAPSLOCK: return "capslock";
         case CODE_SWITCH_ALPHA_SYMBOL: return "symbol";
         case CODE_OUTPUT_TEXT: return "text";
         case CODE_DELETE: return "delete";
@@ -215,10 +219,6 @@
         }
     }
 
-    // Constants for CSV parsing.
-    public static final char CSV_SEPARATOR = ',';
-    public static final char CSV_ESCAPE = '\\';
-
     private Constants() {
         // This utility class is not publicly instantiable.
     }
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index b9db9a0..c99d0e2 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -30,6 +30,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.latin.utils.StringUtils;
+
 import java.util.List;
 import java.util.Locale;
 
@@ -107,7 +109,6 @@
 
     @Override
     public void loadDictionaryAsync() {
-        clearFusionDictionary();
         loadDeviceAccountsEmailAddresses();
         loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
         // TODO: Switch this URL to the newer ContactsContract too
@@ -234,6 +235,11 @@
     }
 
     @Override
+    protected boolean needsToReloadBeforeWriting() {
+        return true;
+    }
+
+    @Override
     protected boolean hasContentChanged() {
         final long startTime = SystemClock.uptimeMillis();
         final int contactCount = getContactCount();
diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
index 534e211..45b2813 100644
--- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java
+++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.utils.JniUtils;
+
 import java.util.Locale;
 
 public final class DicTraverseSession {
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index acd7c2a..7c3e4a7 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -35,8 +35,13 @@
     public static final String TYPE_CONTACTS = "contacts";
     // User dictionary, the system-managed one.
     public static final String TYPE_USER = "user";
-    // User history dictionary internal to LatinIME.
+    // User history dictionary internal to LatinIME. This assumes bigram prediction for now.
     public static final String TYPE_USER_HISTORY = "history";
+    // Personalization binary dictionary internal to LatinIME.
+    public static final String TYPE_PERSONALIZATION = "personalization";
+    // Personalization prediction dictionary internal to LatinIME's Java code.
+    public static final String TYPE_PERSONALIZATION_PREDICTION_IN_JAVA =
+            "personalization_prediction_in_java";
     // Spawned by resuming suggestions. Comes from a span that was in the TextView.
     public static final String TYPE_RESUMED = "resumed";
     protected final String mDictType;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
index ed2b442..d05bb1e 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java
@@ -16,10 +16,11 @@
 
 package com.android.inputmethod.latin;
 
+import android.util.Log;
+
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import android.util.Log;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
index 40e5167..3721132 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java
@@ -21,6 +21,10 @@
 import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -56,7 +60,8 @@
         if (null != assetFileList) {
             for (final AssetFileAddress f : assetFileList) {
                 final BinaryDictionary binaryDictionary = new BinaryDictionary(f.mFilename,
-                        f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN);
+                        f.mOffset, f.mLength, useFullEditDistance, locale, Dictionary.TYPE_MAIN,
+                        false /* isUpdatable */);
                 if (binaryDictionary.isValidDictionary()) {
                     dictList.add(binaryDictionary);
                 }
@@ -109,7 +114,8 @@
                 return null;
             }
             return new BinaryDictionary(sourceDir, afd.getStartOffset(), afd.getLength(),
-                    false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
+                    false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN,
+                    false /* isUpdatable */);
         } catch (android.content.res.Resources.NotFoundException e) {
             Log.e(TAG, "Could not find the resource");
             return null;
@@ -126,21 +132,22 @@
 
     /**
      * Create a dictionary from passed data. This is intended for unit tests only.
-     * @param dictionary the file to read
-     * @param startOffset the offset in the file where the data starts
-     * @param length the length of the data
+     * @param dictionaryList the list of files to read, with their offsets and lengths
      * @param useFullEditDistance whether to use the full edit distance in suggestions
      * @return the created dictionary, or null.
      */
-    public static Dictionary createDictionaryForTest(File dictionary, long startOffset, long length,
+    @UsedForTesting
+    public static Dictionary createDictionaryForTest(final AssetFileAddress[] dictionaryList,
             final boolean useFullEditDistance, Locale locale) {
-        if (dictionary.isFile()) {
-            return new BinaryDictionary(dictionary.getAbsolutePath(), startOffset, length,
-                    useFullEditDistance, locale, Dictionary.TYPE_MAIN);
-        } else {
-            Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
-            return null;
+        final DictionaryCollection dictionaryCollection =
+                new DictionaryCollection(Dictionary.TYPE_MAIN);
+        for (final AssetFileAddress address : dictionaryList) {
+            final BinaryDictionary binaryDictionary = new BinaryDictionary(address.mFilename,
+                    address.mOffset, address.mLength, useFullEditDistance, locale,
+                    Dictionary.TYPE_MAIN, false /* isUpdatable */);
+            dictionaryCollection.addDictionary(binaryDictionary);
         }
+        return dictionaryCollection;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 5609612..2dcfdb0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
+import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
new file mode 100644
index 0000000..8be04c1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -0,0 +1,107 @@
+/*
+ * 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.content.Context;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * An in memory dictionary for memorizing entries and writing a binary dictionary.
+ */
+public class DictionaryWriter extends AbstractDictionaryWriter {
+    // TODO: Regenerate version 3 binary dictionary.
+    private static final int BINARY_DICT_VERSION = 2;
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
+
+    private FusionDictionary mFusionDictionary;
+
+    public DictionaryWriter(final Context context, final String dictType) {
+        super(context, dictType);
+        clear();
+    }
+
+    @Override
+    public void clear() {
+        final HashMap<String, String> attributes = CollectionUtils.newHashMap();
+        mFusionDictionary = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(attributes, false, false));
+    }
+
+    /**
+     * Adds a word unigram to the fusion dictionary.
+     */
+    // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
+    // considering performance regression.
+    @Override
+    public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
+            final boolean isNotAWord) {
+        if (shortcutTarget == null) {
+            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, isNotAWord);
+        }
+    }
+
+    @Override
+    public void addBigramWords(final String word0, final String word1, final int frequency,
+            final boolean isValid) {
+        mFusionDictionary.setBigram(word0, word1, frequency);
+    }
+
+    @Override
+    public void removeBigramWords(final String word0, final String word1) {
+        // This class don't support removing bigram words.
+    }
+
+    @Override
+    protected void writeBinaryDictionary(final FileOutputStream out)
+            throws IOException, UnsupportedFormatException {
+        BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
+    }
+
+    @Override
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final String prevWord, final ProximityInfo proximityInfo,
+            boolean blockOffensiveWords) {
+        // This class doesn't support suggestion.
+        return null;
+    }
+
+    @Override
+    public boolean isValidWord(String word) {
+        // This class doesn't support dictionary retrieval.
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 887d657..3f11391 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,19 +22,12 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * Abstract base class for an expandable dictionary that can be created and updated dynamically
@@ -55,7 +48,7 @@
     /**
      * The maximum length of a word in this dictionary.
      */
-    protected static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+    protected static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
 
     /**
      * A static map of locks, each of which controls access to a single binary dictionary file. They
@@ -75,8 +68,8 @@
      */
     private BinaryDictionary mBinaryDictionary;
 
-    /** The expandable fusion dictionary used to generate the binary dictionary. */
-    private FusionDictionary mFusionDictionary;
+    /** The in-memory dictionary used to generate the binary dictionary. */
+    private AbstractDictionaryWriter mDictionaryWriter;
 
     /**
      * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
@@ -91,10 +84,6 @@
     /** Controls access to the local binary dictionary for this instance. */
     private final DictionaryController mLocalDictionaryController = new DictionaryController();
 
-    private static final int BINARY_DICT_VERSION = 1;
-    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
-            new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
-
     /**
      * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
      * thread.
@@ -136,7 +125,7 @@
         mContext = context;
         mBinaryDictionary = null;
         mSharedDictionaryController = getSharedDictionaryController(filename);
-        clearFusionDictionary();
+        mDictionaryWriter = new DictionaryWriter(context, dictType);
     }
 
     protected static String getFilenameWithLocale(final String name, final String localeStr) {
@@ -149,53 +138,57 @@
     @Override
     public void close() {
         // Ensure that no other threads are accessing the local binary dictionary.
-        mLocalDictionaryController.lock();
+        mLocalDictionaryController.writeLock().lock();
         try {
             if (mBinaryDictionary != null) {
                 mBinaryDictionary.close();
                 mBinaryDictionary = null;
             }
+            mDictionaryWriter.close();
         } finally {
-            mLocalDictionaryController.unlock();
+            mLocalDictionaryController.writeLock().unlock();
         }
     }
 
     /**
-     * Clears the fusion dictionary on the Java side. Note: Does not modify the binary dictionary on
-     * the native side.
+     * Adds a word unigram to the dictionary. Used for loading a dictionary.
      */
-    public void clearFusionDictionary() {
-        final HashMap<String, String> attributes = CollectionUtils.newHashMap();
-        mFusionDictionary = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(attributes, false, false));
+    protected void addWord(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord) {
+        mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
     }
 
     /**
-     * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
-     * are done to update the binary dictionary.
+     * Sets a word bigram in the dictionary. Used for loading a dictionary.
      */
-    // 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,
-            final boolean isNotAWord) {
-        if (shortcutTarget == null) {
-            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, isNotAWord);
-        }
-    }
-
-    /**
-     * Sets a word bigram in the fusion dictionary. Call updateBinaryDictionary when all changes are
-     * done to update the binary dictionary.
-     */
-    // TODO: Create "cache dictionary" to cache fresh bigrams for frequently updated dictionaries,
-    // considering performance regression.
     protected void setBigram(final String prevWord, final String word, final int frequency) {
-        mFusionDictionary.setBigram(prevWord, word, frequency);
+        mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+    }
+
+    /**
+     * Dynamically adds a word unigram to the dictionary.
+     */
+    protected void addWordDynamically(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord) {
+        mLocalDictionaryController.writeLock().lock();
+        try {
+            mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+        } finally {
+            mLocalDictionaryController.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Dynamically sets a word bigram in the dictionary.
+     */
+    protected void setBigramDynamically(final String prevWord, final String word,
+            final int frequency) {
+        mLocalDictionaryController.writeLock().lock();
+        try {
+            mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+        } finally {
+            mLocalDictionaryController.writeLock().unlock();
+        }
     }
 
     @Override
@@ -203,14 +196,29 @@
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords) {
         asyncReloadDictionaryIfRequired();
-        if (mLocalDictionaryController.tryLock()) {
+        // Write lock because getSuggestions in native updates session status.
+        if (mLocalDictionaryController.writeLock().tryLock()) {
             try {
+                final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
+                        mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
+                                blockOffensiveWords);
                 if (mBinaryDictionary != null) {
-                    return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                            blockOffensiveWords);
+                    final ArrayList<SuggestedWordInfo> binarySuggestion =
+                            mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+                                    blockOffensiveWords);
+                    if (inMemDictSuggestion == null) {
+                        return binarySuggestion;
+                    } else if (binarySuggestion == null) {
+                        return inMemDictSuggestion;
+                    } else {
+                        binarySuggestion.addAll(binarySuggestion);
+                        return binarySuggestion;
+                    }
+                } else {
+                    return inMemDictSuggestion;
                 }
             } finally {
-                mLocalDictionaryController.unlock();
+                mLocalDictionaryController.writeLock().unlock();
             }
         }
         return null;
@@ -223,11 +231,11 @@
     }
 
     protected boolean isValidWordInner(final String word) {
-        if (mLocalDictionaryController.tryLock()) {
+        if (mLocalDictionaryController.readLock().tryLock()) {
             try {
                 return isValidWordLocked(word);
             } finally {
-                mLocalDictionaryController.unlock();
+                mLocalDictionaryController.readLock().unlock();
             }
         }
         return false;
@@ -238,22 +246,6 @@
         return mBinaryDictionary.isValidWord(word);
     }
 
-    protected boolean isValidBigram(final String word1, final String word2) {
-        if (mBinaryDictionary == null) return false;
-        return mBinaryDictionary.isValidBigram(word1, word2);
-    }
-
-    protected boolean isValidBigramInner(final String word1, final String word2) {
-        if (mLocalDictionaryController.tryLock()) {
-            try {
-                return isValidBigramLocked(word1, word2);
-            } finally {
-                mLocalDictionaryController.unlock();
-            }
-        }
-        return false;
-    }
-
     protected boolean isValidBigramLocked(final String word1, final String word2) {
         if (mBinaryDictionary == null) return false;
         return mBinaryDictionary.isValidBigram(word1, word2);
@@ -272,7 +264,7 @@
      * Loads the current binary dictionary from internal storage. Assumes the dictionary file
      * exists.
      */
-    protected void loadBinaryDictionary() {
+    private void loadBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
                     + mSharedDictionaryController.mLastUpdateRequestTime + " update="
@@ -285,15 +277,18 @@
 
         // Build the new binary dictionary
         final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length,
-                true /* useFullEditDistance */, null, mDictType);
+                true /* useFullEditDistance */, null, mDictType, false /* isUpdatable */);
 
         if (mBinaryDictionary != null) {
             // Ensure all threads accessing the current dictionary have finished before swapping in
             // the new one.
             final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
-            mLocalDictionaryController.lock();
-            mBinaryDictionary = newBinaryDictionary;
-            mLocalDictionaryController.unlock();
+            mLocalDictionaryController.writeLock().lock();
+            try {
+                mBinaryDictionary = newBinaryDictionary;
+            } finally {
+                mLocalDictionaryController.writeLock().unlock();
+            }
             oldBinaryDictionary.close();
         } else {
             mBinaryDictionary = newBinaryDictionary;
@@ -301,6 +296,12 @@
     }
 
     /**
+     * Abstract method for checking if it is required to reload the dictionary before writing
+     * a binary dictionary.
+     */
+    abstract protected boolean needsToReloadBeforeWriting();
+
+    /**
      * Generates and writes a new binary dictionary based on the contents of the fusion dictionary.
      */
     private void generateBinaryDictionary() {
@@ -309,33 +310,11 @@
                     + mSharedDictionaryController.mLastUpdateRequestTime + " update="
                     + mSharedDictionaryController.mLastUpdateTime);
         }
-
-        loadDictionaryAsync();
-
-        final String tempFileName = mFilename + ".temp";
-        final File file = new File(mContext.getFilesDir(), mFilename);
-        final File tempFile = new File(mContext.getFilesDir(), tempFileName);
-        FileOutputStream out = null;
-        try {
-            out = new FileOutputStream(tempFile);
-            BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
-            out.flush();
-            out.close();
-            tempFile.renameTo(file);
-            clearFusionDictionary();
-        } catch (IOException e) {
-            Log.e(TAG, "IO exception while writing file", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "Unsupported format", e);
-        } finally {
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-            }
+        if (needsToReloadBeforeWriting()) {
+            mDictionaryWriter.clear();
+            loadDictionaryAsync();
         }
+        mDictionaryWriter.write(mFilename);
     }
 
     /**
@@ -388,7 +367,7 @@
     private final void syncReloadDictionaryInternal() {
         // Ensure that only one thread attempts to read or write to the shared binary dictionary
         // file at the same time.
-        mSharedDictionaryController.lock();
+        mSharedDictionaryController.writeLock().lock();
         try {
             final long time = SystemClock.uptimeMillis();
             final boolean dictionaryFileExists = dictionaryFileExists();
@@ -414,9 +393,15 @@
                 // shared dictionary.
                 loadBinaryDictionary();
             }
+            if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+                // Binary dictionary is not valid. Regenerate the dictionary file.
+                mSharedDictionaryController.mLastUpdateTime = time;
+                generateBinaryDictionary();
+                loadBinaryDictionary();
+            }
             mLocalDictionaryController.mLastUpdateTime = time;
         } finally {
-            mSharedDictionaryController.unlock();
+            mSharedDictionaryController.writeLock().unlock();
         }
     }
 
@@ -441,7 +426,7 @@
      * dictionary is out of date. Can be shared across multiple dictionary instances that access the
      * same filename.
      */
-    private static class DictionaryController extends ReentrantLock {
+    private static class DictionaryController extends ReentrantReadWriteLock {
         private volatile long mLastUpdateTime = 0;
         private volatile long mLastUpdateRequestTime = 0;
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0dabdb8..bd2d703 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -22,7 +22,8 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.util.ArrayList;
 import java.util.LinkedList;
@@ -42,7 +43,7 @@
     protected static final int BIGRAM_MAX_FREQUENCY = 255;
 
     private Context mContext;
-    private char[] mWordBuilder = new char[Constants.Dictionary.MAX_WORD_LENGTH];
+    private char[] mWordBuilder = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
     private int mMaxDepth;
     private int mInputLength;
 
@@ -86,7 +87,7 @@
         }
     }
 
-    protected interface NextWord {
+    public interface NextWord {
         public Node getWordNode();
         public int getFrequency();
         public ForgettingCurveParams getFcParams();
@@ -160,7 +161,7 @@
         super(dictType);
         mContext = context;
         clearDictionary();
-        mCodes = new int[Constants.Dictionary.MAX_WORD_LENGTH][];
+        mCodes = new int[Constants.DICTIONARY_MAX_WORD_LENGTH][];
     }
 
     public void loadDictionary() {
@@ -197,11 +198,11 @@
     }
 
     public int getMaxWordLength() {
-        return Constants.Dictionary.MAX_WORD_LENGTH;
+        return Constants.DICTIONARY_MAX_WORD_LENGTH;
     }
 
     public void addWord(final String word, final String shortcutTarget, final int frequency) {
-        if (word.length() >= Constants.Dictionary.MAX_WORD_LENGTH) {
+        if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
             return;
         }
         addWordRec(mRoots, word, 0, shortcutTarget, frequency, null);
@@ -257,7 +258,7 @@
             final boolean blockOffensiveWords) {
         if (reloadDictionaryIfRequired()) return null;
         if (composer.size() > 1) {
-            if (composer.size() >= Constants.Dictionary.MAX_WORD_LENGTH) {
+            if (composer.size() >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
                 return null;
             }
             final ArrayList<SuggestedWordInfo> suggestions =
@@ -628,7 +629,7 @@
     }
 
     // Local to reverseLookUp, but do not allocate each time.
-    private final char[] mLookedUpString = new char[Constants.Dictionary.MAX_WORD_LENGTH];
+    private final char[] mLookedUpString = new char[Constants.DICTIONARY_MAX_WORD_LENGTH];
 
     /**
      * reverseLookUp retrieves the full word given a list of terminal nodes and adds those words
@@ -643,7 +644,7 @@
         for (NextWord nextWord : terminalNodes) {
             node = nextWord.getWordNode();
             freq = nextWord.getFrequency();
-            int index = Constants.Dictionary.MAX_WORD_LENGTH;
+            int index = Constants.DICTIONARY_MAX_WORD_LENGTH;
             do {
                 --index;
                 mLookedUpString[index] = node.mCode;
@@ -655,7 +656,7 @@
             // to ignore the word in this case.
             if (freq >= 0 && node == null) {
                 suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
-                        Constants.Dictionary.MAX_WORD_LENGTH - index),
+                        Constants.DICTIONARY_MAX_WORD_LENGTH - index),
                         freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
             }
         }
@@ -727,172 +728,206 @@
      * to their base characters.  If c is in range, BASE_CHARS[c] == c
      * if c is not a combined character, or the base character if it
      * is combined.
+     *
+     * cf. native/jni/src/utils/char_utils.cpp
      */
     private static final char BASE_CHARS[] = {
-        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
-        0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
-        0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
-        0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
-        0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
-        0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
-        0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
-        0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
-        0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
-        0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
-        0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
-        0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
-        0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
-        0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
-        0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
-        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
-        0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
-        0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
-        0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
-        0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
-        0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
-        0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020,
-        0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7,
-        0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf,
-        0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043,
-        0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
-        0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7,
-        0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f
-                                                                        // Manually changed df to 73
-        0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
-        0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
-        0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7,
-        0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f
-        0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
-        0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
-        0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
-        0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
-        0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
-        0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
-        0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b,
-        0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c,
-        0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e,
-        0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f,
-        0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
-        0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
-        0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
-        0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
-        0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
-        0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073,
-        0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
-        0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f,
-        0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
-        0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f,
-        0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7,
-        0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055,
-        0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7,
-        0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf,
-        0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c,
-        0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049,
-        0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc,
-        0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4,
-        0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067,
-        0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292,
-        0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7,
-        0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8,
-        0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
-        0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f,
-        0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
-        0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068,
-        0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
-        0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f,
-        0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
-        0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f,
-        0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
-        0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f,
-        0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
-        0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
-        0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
-        0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
-        0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
-        0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
-        0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
-        0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
-        0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
-        0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
-        0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
-        0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
-        0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077,
-        0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
-        0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
-        0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
-        0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
-        0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df,
-        0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7,
-        0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
-        0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
-        0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff,
-        0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
-        0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f,
-        0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
-        0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f,
-        0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
-        0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f,
-        0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
-        0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f,
-        0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
-        0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f,
-        0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
-        0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
-        0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
-        0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
-        0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377,
-        0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f,
-        0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7,
-        0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9,
-        0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
-        0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f,
-        0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7,
-        0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9,
-        0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
-        0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
-        0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
-        0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf,
-        0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7,
-        0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df,
-        0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7,
-        0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef,
-        0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7,
-        0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff,
-        0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
-        0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f,
-        0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
-        0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
-        0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
-        0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
-        0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
-        0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
-        0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
-        0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
-        0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
-        0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
-        0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
-        0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f,
-        0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
-        0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f,
-        0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
-        0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
-        0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
-        0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f,
-        0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7,
-        0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
-        0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7,
-        0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf,
-        0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7,
-        0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf,
-        0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435,
-        0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437,
-        0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e,
-        0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443,
-        0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7,
-        0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff,
+        /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+        /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+        /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+        /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
+        /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+        /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+        /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+        /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+        /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+        /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+        /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+        /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+        /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+        /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+        /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+        /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
+        /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+        /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
+        /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+        /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
+        /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+        /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
+        /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
+        /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
+        /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
+        /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
+        /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
+        /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
+            // U+00D8: Manually changed from 00D8 to 004F
+              // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+            // U+00DF: Manually changed from 00DF to 0073
+        /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
+        /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+        /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
+        /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
+            // U+00F8: Manually changed from 00F8 to 006F
+              // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+        /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
+        /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
+        /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
+        /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
+        /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
+        /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
+        /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
+        /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
+        /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
+            // U+0141: Manually changed from 0141 to 004C
+            // U+0142: Manually changed from 0142 to 006C
+        /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
+        /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
+        /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
+        /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
+        /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
+        /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
+        /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
+        /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
+        /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
+        /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
+        /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
+        /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
+        /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
+        /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
+        /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
+        /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
+        /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
+        /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
+            // U+01D5: Manually changed from 00DC to 0055
+            // U+01D6: Manually changed from 00FC to 0075
+            // U+01D7: Manually changed from 00DC to 0055
+        /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
+            // U+01D8: Manually changed from 00FC to 0075
+            // U+01D9: Manually changed from 00DC to 0055
+            // U+01DA: Manually changed from 00FC to 0075
+            // U+01DB: Manually changed from 00DC to 0055
+            // U+01DC: Manually changed from 00FC to 0075
+            // U+01DE: Manually changed from 00C4 to 0041
+            // U+01DF: Manually changed from 00E4 to 0061
+        /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
+            // U+01E0: Manually changed from 0226 to 0041
+            // U+01E1: Manually changed from 0227 to 0061
+        /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
+            // U+01EC: Manually changed from 01EA to 004F
+            // U+01ED: Manually changed from 01EB to 006F
+        /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
+        /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
+            // U+01FA: Manually changed from 00C5 to 0041
+            // U+01FB: Manually changed from 00E5 to 0061
+            // U+01FE: Manually changed from 00D8 to 004F
+              // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+            // U+01FF: Manually changed from 00F8 to 006F
+              // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+        /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
+        /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
+        /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
+        /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
+        /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
+        /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
+            // U+022A: Manually changed from 00D6 to 004F
+            // U+022B: Manually changed from 00F6 to 006F
+            // U+022C: Manually changed from 00D5 to 004F
+            // U+022D: Manually changed from 00F5 to 006F
+        /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
+            // U+0230: Manually changed from 022E to 004F
+            // U+0231: Manually changed from 022F to 006F
+        /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
+        /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
+        /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
+        /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+        /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
+        /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+        /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
+        /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+        /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
+        /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+        /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
+        /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+        /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
+        /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
+        /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
+        /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
+        /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
+        /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
+        /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
+        /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
+        /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
+        /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
+        /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
+        /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
+        /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
+        /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+        /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
+        /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+        /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
+        /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+        /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
+        /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+        /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
+        /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
+        /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
+        /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+        /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
+        /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+        /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
+        /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
+        /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
+        /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
+        /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
+        /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
+        /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
+        /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
+        /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
+        /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+        /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+        /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+        /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
+        /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
+        /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
+        /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
+        /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
+        /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
+        /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
+        /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
+        /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
+        /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
+        /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+            // U+0419: Manually changed from 0418 to 0419
+        /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
+        /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+            // U+042A: Manually changed from 042A to 042C
+        /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+        /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+            // U+0439: Manually changed from 0438 to 0439
+        /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+        /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+            // U+044A: Manually changed from 044A to 044C
+        /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+        /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
+        /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
+        /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
+        /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
+        /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
+        /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+        /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
+        /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
+        /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
+        /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
+        /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
+        /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
+        /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
+        /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
+        /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
+        /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
+        /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
+        /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
+        /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
+        /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
+        /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
     };
-
-    // generated with:
-    // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
-
 }
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index dd58db5..21b103e 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -20,6 +20,9 @@
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
 /**
  * Class to hold attributes of the input field.
  */
@@ -199,6 +202,6 @@
         if (editorInfo == null) return false;
         final String findingKey = (packageName != null) ? packageName + "." + key
                 : key;
-        return StringUtils.containsInCsv(findingKey, editorInfo.privateImeOptions);
+        return StringUtils.containsInCommaSplittableText(findingKey, editorInfo.privateImeOptions);
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java
index 81c8330..e96a46e 100644
--- a/java/src/com/android/inputmethod/latin/InputPointers.java
+++ b/java/src/com/android/inputmethod/latin/InputPointers.java
@@ -17,6 +17,7 @@
 package com.android.inputmethod.latin;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.ResizableIntArray;
 
 import android.util.Log;
 
diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java
index 826dc11..642b3a4 100644
--- a/java/src/com/android/inputmethod/latin/LastComposedWord.java
+++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.utils.StringUtils;
+
 import android.text.TextUtils;
 
 /**
@@ -47,7 +49,7 @@
     public final String mPrevWord;
     public final int mCapitalizedMode;
     public final InputPointers mInputPointers =
-            new InputPointers(Constants.Dictionary.MAX_WORD_LENGTH);
+            new InputPointers(Constants.DICTIONARY_MAX_WORD_LENGTH);
 
     private boolean mActive;
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c464a70..719c7c8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -43,7 +43,6 @@
 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;
@@ -74,11 +73,29 @@
 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.personalization.PersonalizationDictionaryHelper;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.settings.SettingsActivity;
+import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.CompletionInfoUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.IntentUtils;
+import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
+import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.io.FileDescriptor;
@@ -139,7 +156,7 @@
     private SuggestionStripView mSuggestionStripView;
     // Never null
     private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
-    @UsedForTesting Suggest mSuggest;
+    private Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
     private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
 
@@ -153,7 +170,8 @@
 
     private boolean mIsMainDictionaryAvailable;
     private UserBinaryDictionary mUserDictionary;
-    private UserHistoryDictionary mUserHistoryDictionary;
+    private UserHistoryPredictionDictionary mUserHistoryPredictionDictionary;
+    private PersonalizationPredictionDictionary mPersonalizationPredictionDictionary;
     private boolean mIsUserDictionaryAvailable;
 
     private LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD;
@@ -179,11 +197,8 @@
     private int mDisplayOrientation;
 
     // Object for reacting to adding/removing a dictionary pack.
-    // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-    // Service yet.
     private BroadcastReceiver mDictionaryPackInstallReceiver =
-            ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS
-                    ? null : new DictionaryPackInstallBroadcastReceiver(this);
+            new DictionaryPackInstallBroadcastReceiver(this);
 
     // Keeps track of most recently inserted text (multi-character key) for reverting
     private String mEnteredText;
@@ -204,6 +219,7 @@
         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 MSG_REOPEN_DICTIONARIES = 5;
 
         private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1;
 
@@ -244,6 +260,13 @@
             case MSG_RESUME_SUGGESTIONS:
                 latinIme.restartSuggestionsOnWordTouchedByCursor();
                 break;
+            case MSG_REOPEN_DICTIONARIES:
+                latinIme.initSuggest();
+                // In theory we could call latinIme.updateSuggestionStrip() right away, but
+                // in the practice, the dictionary is not finished opening yet so we wouldn't
+                // get any suggestions. Wait one frame.
+                postUpdateSuggestionStrip();
+                break;
             }
         }
 
@@ -251,6 +274,10 @@
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION_STRIP), mDelayUpdateSuggestions);
         }
 
+        public void postReopenDictionaries() {
+            sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES));
+        }
+
         public void postResumeSuggestions() {
             removeMessages(MSG_RESUME_SUGGESTIONS);
             sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions);
@@ -264,6 +291,10 @@
             return hasMessages(MSG_UPDATE_SUGGESTION_STRIP);
         }
 
+        public boolean hasPendingReopenDictionaries() {
+            return hasMessages(MSG_REOPEN_DICTIONARIES);
+        }
+
         public void postUpdateShiftState() {
             removeMessages(MSG_UPDATE_SHIFT_STATE);
             sendMessageDelayed(obtainMessage(MSG_UPDATE_SHIFT_STATE), mDelayUpdateShiftState);
@@ -416,6 +447,12 @@
         }
     }
 
+    // Loading the native library eagerly to avoid unexpected UnsatisfiedLinkError at the initial
+    // JNI call as much as possible.
+    static {
+        JniUtils.loadNativeLibrary();
+    }
+
     public LatinIME() {
         super();
         mSettings = Settings.getInstance();
@@ -458,19 +495,15 @@
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
         registerReceiver(mReceiver, filter);
 
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet.
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            final IntentFilter packageFilter = new IntentFilter();
-            packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-            packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-            packageFilter.addDataScheme(SCHEME_PACKAGE);
-            registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
+        final IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme(SCHEME_PACKAGE);
+        registerReceiver(mDictionaryPackInstallReceiver, packageFilter);
 
-            final IntentFilter newDictFilter = new IntentFilter();
-            newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
-            registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
-        }
+        final IntentFilter newDictFilter = new IntentFilter();
+        newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION);
+        registerReceiver(mDictionaryPackInstallReceiver, newDictFilter);
     }
 
     // Has to be package-visible for unit tests
@@ -480,8 +513,18 @@
         final InputAttributes inputAttributes =
                 new InputAttributes(getCurrentInputEditorInfo(), isFullscreenMode());
         mSettings.loadSettings(locale, inputAttributes);
-        // May need to reset the contacts dictionary depending on the user settings.
-        resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+        AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(mSettings.getCurrent());
+        // To load the keyboard we need to load all the settings once, but resetting the
+        // contacts dictionary should be deferred until after the new layout has been displayed
+        // to improve responsivity. In the language switching process, we post a reopenDictionaries
+        // message, then come here to read the settings for the new language before we change
+        // the layout; at this time, we need to skip resetting the contacts dictionary. It will
+        // be done later inside {@see #initSuggest()} when the reopenDictionaries message is
+        // processed.
+        if (!mHandler.hasPendingReopenDictionaries()) {
+            // May need to reset the contacts dictionary depending on the user settings.
+            resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+        }
     }
 
     // Note that this method is called from a non-UI thread.
@@ -498,33 +541,35 @@
         final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
         final String localeStr = subtypeLocale.toString();
 
-        final ContactsBinaryDictionary oldContactsDictionary;
-        if (mSuggest != null) {
-            oldContactsDictionary = mSuggest.getContactsDictionary();
-            mSuggest.close();
-        } else {
-            oldContactsDictionary = null;
-        }
-        mSuggest = new Suggest(this /* Context */, subtypeLocale,
+        final Suggest newSuggest = new Suggest(this /* Context */, subtypeLocale,
                 this /* SuggestInitializationListener */);
-        if (mSettings.getCurrent().mCorrectionEnabled) {
-            mSuggest.setAutoCorrectionThreshold(mSettings.getCurrent().mAutoCorrectionThreshold);
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.mCorrectionEnabled) {
+            newSuggest.setAutoCorrectionThreshold(settingsValues.mAutoCorrectionThreshold);
         }
 
         mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.getInstance().initSuggest(mSuggest);
+            ResearchLogger.getInstance().initSuggest(newSuggest);
         }
 
         mUserDictionary = new UserBinaryDictionary(this, localeStr);
         mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
-        mSuggest.setUserDictionary(mUserDictionary);
-
-        resetContactsDictionary(oldContactsDictionary);
+        newSuggest.setUserDictionary(mUserDictionary);
 
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-        mUserHistoryDictionary = UserHistoryDictionary.getInstance(this, localeStr, prefs);
-        mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
+
+        mUserHistoryPredictionDictionary = PersonalizationDictionaryHelper
+                .getUserHistoryPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setUserHistoryPredictionDictionary(mUserHistoryPredictionDictionary);
+        mPersonalizationPredictionDictionary = PersonalizationDictionaryHelper
+                .getPersonalizationPredictionDictionary(this, localeStr, prefs);
+        newSuggest.setPersonalizationPredictionDictionary(mPersonalizationPredictionDictionary);
+
+        final Suggest oldSuggest = mSuggest;
+        resetContactsDictionary(null != oldSuggest ? oldSuggest.getContactsDictionary() : null);
+        mSuggest = newSuggest;
+        if (oldSuggest != null) oldSuggest.close();
     }
 
     /**
@@ -536,8 +581,9 @@
      * @param oldContactsDictionary an optional dictionary to use, or null
      */
     private void resetContactsDictionary(final ContactsBinaryDictionary oldContactsDictionary) {
+        final Suggest suggest = mSuggest;
         final boolean shouldSetDictionary =
-                (null != mSuggest && mSettings.getCurrent().mUseContactsDict);
+                (null != suggest && mSettings.getCurrent().mUseContactsDict);
 
         final ContactsBinaryDictionary dictionaryToUse;
         if (!shouldSetDictionary) {
@@ -564,8 +610,8 @@
             }
         }
 
-        if (null != mSuggest) {
-            mSuggest.setContactsDictionary(dictionaryToUse);
+        if (null != suggest) {
+            suggest.setContactsDictionary(dictionaryToUse);
         }
     }
 
@@ -577,8 +623,9 @@
 
     @Override
     public void onDestroy() {
-        if (mSuggest != null) {
-            mSuggest.close();
+        final Suggest suggest = mSuggest;
+        if (suggest != null) {
+            suggest.close();
             mSuggest = null;
         }
         mSettings.onDestroy();
@@ -586,11 +633,7 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.getInstance().onDestroy();
         }
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet.
-        if (!ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            unregisterReceiver(mDictionaryPackInstallReceiver);
-        }
+        unregisterReceiver(mDictionaryPackInstallReceiver);
         LatinImeLogger.commit();
         LatinImeLogger.onDestroy();
         super.onDestroy();
@@ -676,7 +719,9 @@
         super.onStartInputView(editorInfo, restarting);
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         final MainKeyboardView mainKeyboardView = switcher.getMainKeyboardView();
-        final SettingsValues currentSettings = mSettings.getCurrent();
+        // If we are starting input in a different text field from before, we'll have to reload
+        // settings, so currentSettingsValues can't be final.
+        SettingsValues currentSettingsValues = mSettings.getCurrent();
 
         if (editorInfo == null) {
             Log.e(TAG, "Null EditorInfo in onStartInputView()");
@@ -731,7 +776,7 @@
             accessUtils.onStartInputViewInternal(mainKeyboardView, editorInfo, restarting);
         }
 
-        final boolean inputTypeChanged = !currentSettings.isSameInputType(editorInfo);
+        final boolean inputTypeChanged = !currentSettingsValues.isSameInputType(editorInfo);
         final boolean isDifferentTextField = !restarting || inputTypeChanged;
         if (isDifferentTextField) {
             mSubtypeSwitcher.updateParametersOnStartInputView();
@@ -746,6 +791,7 @@
         // span, so we should reset our state unconditionally, even if restarting is true.
         mEnteredText = null;
         resetComposingState(true /* alsoResetLastComposedWord */);
+        if (isDifferentTextField) mHandler.postResumeSuggestions();
         mDeleteCount = 0;
         mSpaceState = SPACE_STATE_NONE;
         mRecapitalizeStatus.deactivate();
@@ -753,7 +799,8 @@
 
         // Note: the following does a round-trip IPC on the main thread: be careful
         final Locale currentLocale = mSubtypeSwitcher.getCurrentSubtypeLocale();
-        if (null != mSuggest && null != currentLocale && !currentLocale.equals(mSuggest.mLocale)) {
+        final Suggest suggest = mSuggest;
+        if (null != suggest && null != currentLocale && !currentLocale.equals(suggest.mLocale)) {
             initSuggest();
         }
         if (mSuggestionStripView != null) {
@@ -769,12 +816,13 @@
         if (isDifferentTextField) {
             mainKeyboardView.closing();
             loadSettings();
+            currentSettingsValues = mSettings.getCurrent();
 
-            if (mSuggest != null && currentSettings.mCorrectionEnabled) {
-                mSuggest.setAutoCorrectionThreshold(currentSettings.mAutoCorrectionThreshold);
+            if (suggest != null && currentSettingsValues.mCorrectionEnabled) {
+                suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold);
             }
 
-            switcher.loadKeyboard(editorInfo, currentSettings);
+            switcher.loadKeyboard(editorInfo, currentSettingsValues);
         } else if (restarting) {
             // TODO: Come up with a more comprehensive way to reset the keyboard layout when
             // a keyboard layout set doesn't get reloaded in this method.
@@ -795,14 +843,14 @@
         mHandler.cancelDoubleSpacePeriodTimer();
 
         mainKeyboardView.setMainDictionaryAvailability(mIsMainDictionaryAvailable);
-        mainKeyboardView.setKeyPreviewPopupEnabled(currentSettings.mKeyPreviewPopupOn,
-                currentSettings.mKeyPreviewPopupDismissDelay);
+        mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn,
+                currentSettingsValues.mKeyPreviewPopupDismissDelay);
         mainKeyboardView.setSlidingKeyInputPreviewEnabled(
-                currentSettings.mSlidingKeyInputPreviewEnabled);
+                currentSettingsValues.mSlidingKeyInputPreviewEnabled);
         mainKeyboardView.setGestureHandlingEnabledByUser(
-                currentSettings.mGestureInputEnabled);
-        mainKeyboardView.setGesturePreviewMode(currentSettings.mGesturePreviewTrailEnabled,
-                currentSettings.mGestureFloatingPreviewTextEnabled);
+                currentSettingsValues.mGestureInputEnabled);
+        mainKeyboardView.setGesturePreviewMode(currentSettingsValues.mGesturePreviewTrailEnabled,
+                currentSettingsValues.mGestureFloatingPreviewTextEnabled);
 
         // If we have a user dictionary addition in progress, we should check now if we should
         // replace the previously committed string with the word that has actually been added
@@ -850,11 +898,15 @@
         mKeyboardSwitcher.onFinishInputView();
         final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
         if (mainKeyboardView != null) {
-            mainKeyboardView.cancelAllMessages();
+            mainKeyboardView.cancelAllOngoingEvents();
+            mainKeyboardView.deallocateMemory();
         }
         // Remove pending messages related to update suggestions
         mHandler.cancelUpdateSuggestionStrip();
+        // Should do the following in onFinishInputInternal but until JB MR2 it's not called :(
+        if (mWordComposer.isComposingWord()) mConnection.finishComposingText();
         resetComposingState(true /* alsoResetLastComposedWord */);
+        mRichImm.clearSubtypeCaches();
         // Notify ResearchLogger
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
@@ -891,20 +943,17 @@
             }
         }
 
-        // TODO: refactor the following code to be less contrived.
-        // "newSelStart != composingSpanEnd" || "newSelEnd != composingSpanEnd" means
-        // that the cursor is not at the end of the composing span, or there is a selection.
-        // "mLastSelectionStart != newSelStart" means that the cursor is not in the same place
-        // as last time we were called (if there is a selection, it means the start hasn't
-        // changed, so it's the end that did).
-        final boolean selectionChanged = (newSelStart != composingSpanEnd
-                || newSelEnd != composingSpanEnd) && mLastSelectionStart != newSelStart;
+        final boolean selectionChanged = mLastSelectionStart != newSelStart
+                || mLastSelectionEnd != newSelEnd;
+
         // if composingSpanStart and composingSpanEnd are -1, it means there is no composing
         // span in the view - we can use that to narrow down whether the cursor was moved
         // by us or not. If we are composing a word but there is no composing span, then
         // we know for sure the cursor moved while we were composing and we should reset
-        // the state.
+        // the state. TODO: rescind this policy: the framework never removes the composing
+        // span on its own accord while editing. This test is useless.
         final boolean noComposingSpan = composingSpanStart == -1 && composingSpanEnd == -1;
+
         // If the keyboard is not visible, we don't need to do all the housekeeping work, as it
         // will be reset when the keyboard shows up anyway.
         // TODO: revisit this when LatinIME supports hardware keyboards.
@@ -926,7 +975,14 @@
             // state-related special processing to kick in.
             mSpaceState = SPACE_STATE_NONE;
 
-            if ((!mWordComposer.isComposingWord()) || selectionChanged || noComposingSpan) {
+            // TODO: is it still necessary to test for composingSpan related stuff?
+            final boolean selectionChangedOrSafeToReset = selectionChanged
+                    || (!mWordComposer.isComposingWord()) || noComposingSpan;
+            final boolean hasOrHadSelection = (oldSelStart != oldSelEnd
+                    || newSelStart != newSelEnd);
+            final int moveAmount = newSelStart - oldSelStart;
+            if (selectionChangedOrSafeToReset && (hasOrHadSelection
+                    || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
                 // If we are composing a word and moving the cursor, we would want to set a
                 // suggestion span for recorrection to work correctly. Unfortunately, that
                 // would involve the keyboard committing some new text, which would move the
@@ -937,6 +993,13 @@
                 // text, but that is probably too expensive to do, so we decided to leave things
                 // as is.
                 resetEntireInputState(newSelStart);
+            } else {
+                // resetEntireInputState calls resetCachesUponCursorMove, but with the second
+                // argument as true. But in all cases where we don't reset the entire input state,
+                // we still want to tell the rich input connection about the new cursor position so
+                // that it can update its caches.
+                mConnection.resetCachesUponCursorMove(newSelStart,
+                        false /* shouldFinishComposition */);
             }
 
             // We moved the cursor. If we are touching a word, we need to resume suggestion,
@@ -1160,10 +1223,11 @@
     private void resetEntireInputState(final int newCursorPosition) {
         final boolean shouldFinishComposition = mWordComposer.isComposingWord();
         resetComposingState(true /* alsoResetLastComposedWord */);
-        if (mSettings.getCurrent().mBigramPredictionEnabled) {
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(settingsValues.mSuggestPuncList, false);
         }
         mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition);
     }
@@ -1237,8 +1301,9 @@
     }
 
     private boolean maybeDoubleSpacePeriod() {
-        if (!mSettings.getCurrent().mCorrectionEnabled) return false;
-        if (!mSettings.getCurrent().mUseDoubleSpacePeriod) return false;
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (!settingsValues.mCorrectionEnabled) return false;
+        if (!settingsValues.mUseDoubleSpacePeriod) return false;
         if (!mHandler.isAcceptingDoubleSpacePeriod()) return false;
         final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0);
         if (lastThree != null && lastThree.length() == 3
@@ -1314,14 +1379,11 @@
         showSubtypeSelectorAndSettings();
     }
 
-    // Virtual codes representing custom requests.  These are used in onCustomRequest() below.
-    public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
-
     @Override
     public boolean onCustomRequest(final int requestCode) {
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
-        case CODE_SHOW_INPUT_METHOD_PICKER:
+        case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
             if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 mRichImm.getInputMethodManager().showInputMethodPicker();
                 return true;
@@ -1418,8 +1480,8 @@
             LatinImeLogger.logOnDelete(x, y);
             break;
         case Constants.CODE_SHIFT:
-            // Note: calling back to the keyboard on Shift key is handled in onPressKey()
-            // and onReleaseKey().
+            // Note: Calling back to the keyboard on Shift key is handled in
+            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             final Keyboard currentKeyboard = switcher.getKeyboard();
             if (null != currentKeyboard && currentKeyboard.mId.isAlphabetKeyboard()) {
                 // TODO: Instead of checking for alphabetic keyboard here, separate keycodes for
@@ -1427,9 +1489,13 @@
                 handleRecapitalize();
             }
             break;
+        case Constants.CODE_CAPSLOCK:
+            // Note: Changing keyboard to shift lock state is handled in
+            // {@link KeyboardSwitcher#onCodeInput(int)}.
+            break;
         case Constants.CODE_SWITCH_ALPHA_SYMBOL:
-            // Note: calling back to the keyboard on symbol key is handled in onPressKey()
-            // and onReleaseKey().
+            // Note: Calling back to the keyboard on symbol key is handled in
+            // {@link #onPressKey(int,boolean)} and {@link #onReleaseKey(int,boolean)}.
             break;
         case Constants.CODE_SETTINGS:
             onSettingsKeyPressed();
@@ -1482,8 +1548,9 @@
             break;
         }
         switcher.onCodeInput(primaryCode);
-        // Reset after any single keystroke, except shift and symbol-shift
+        // Reset after any single keystroke, except shift, capslock, and symbol-shift
         if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT
+                && primaryCode != Constants.CODE_CAPSLOCK
                 && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL)
             mLastComposedWord.deactivate();
         if (Constants.CODE_DELETE != primaryCode) {
@@ -1496,14 +1563,15 @@
             final int spaceState) {
         mSpaceState = SPACE_STATE_NONE;
         final boolean didAutoCorrect;
-        if (mSettings.getCurrent().isWordSeparator(primaryCode)) {
+        final SettingsValues settingsValues = mSettings.getCurrent();
+        if (settingsValues.isWordSeparator(primaryCode)) {
             didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState);
         } else {
             didAutoCorrect = false;
             if (SPACE_STATE_PHANTOM == spaceState) {
-                if (mSettings.isInternal()) {
+                if (settingsValues.mIsInternal) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
-                        Stats.onAutoCorrection(
+                        LatinImeLoggerUtils.onAutoCorrection(
                                 "", mWordComposer.getTypedWord(), " ", mWordComposer);
                     }
                 }
@@ -1561,10 +1629,12 @@
         BatchInputUpdater.getInstance().onStartBatchInput(this);
         mHandler.cancelUpdateSuggestionStrip();
         mConnection.beginBatchEdit();
+        final SettingsValues settingsValues = mSettings.getCurrent();
         if (mWordComposer.isComposingWord()) {
-            if (mSettings.isInternal()) {
+            if (settingsValues.mIsInternal) {
                 if (mWordComposer.isBatchMode()) {
-                    Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
+                    LatinImeLoggerUtils.onAutoCorrection(
+                            "", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
             }
             final int wordComposerSize = mWordComposer.size();
@@ -1590,7 +1660,7 @@
         }
         final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor();
         if (Character.isLetterOrDigit(codePointBeforeCursor)
-                || mSettings.getCurrent().isUsuallyFollowedBySpace(codePointBeforeCursor)) {
+                || settingsValues.isUsuallyFollowedBySpace(codePointBeforeCursor)) {
             mSpaceState = SPACE_STATE_PHANTOM;
         }
         mConnection.endBatchEdit();
@@ -1635,8 +1705,10 @@
         public void onStartBatchInput(final LatinIME latinIme) {
             synchronized (mLock) {
                 mHandler.removeMessages(MSG_UPDATE_GESTURE_PREVIEW_AND_SUGGESTION_STRIP);
-                mLatinIme = latinIme;
                 mInBatchInput = true;
+                mLatinIme = latinIme;
+                mLatinIme.mHandler.showGesturePreviewAndSuggestionStrip(
+                        SuggestedWords.EMPTY, false /* dismissGestureFloatingPreviewText */);
             }
         }
 
@@ -1795,8 +1867,6 @@
                     if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                         final String word = mWordComposer.getTypedWord();
                         ResearchLogger.latinIME_handleBackspace_batch(word, 1);
-                        ResearchLogger.getInstance().uncommitCurrentLogUnit(
-                                word, false /* dumpCurrentLogUnit */);
                     }
                     final String rejectedSuggestion = mWordComposer.getTypedWord();
                     mWordComposer.reset();
@@ -1810,9 +1880,10 @@
                 mConnection.deleteSurroundingText(1, 0);
             }
         } else {
+            final SettingsValues currentSettings = mSettings.getCurrent();
             if (mLastComposedWord.canRevertCommit()) {
-                if (mSettings.isInternal()) {
-                    Stats.onAutoCorrectionCancellation();
+                if (currentSettings.mIsInternal) {
+                    LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
                 revertCommit();
                 return;
@@ -1823,6 +1894,9 @@
                 // like the smiley key or the .com key.
                 final int length = mEnteredText.length();
                 mConnection.deleteSurroundingText(length, 0);
+                if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+                    ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText);
+                }
                 mEnteredText = null;
                 // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false.
                 // In addition we know that spaceState is false, and that we should not be
@@ -1856,7 +1930,8 @@
                 mLastSelectionEnd = mLastSelectionStart;
                 mConnection.deleteSurroundingText(numCharsDeleted, 0);
                 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                    ResearchLogger.latinIME_handleBackspace(numCharsDeleted);
+                    ResearchLogger.latinIME_handleBackspace(numCharsDeleted,
+                            false /* shouldUncommitLogUnit */);
                 }
             } else {
                 // There is no selection, just delete one character.
@@ -1874,16 +1949,17 @@
                     mConnection.deleteSurroundingText(1, 0);
                 }
                 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                    ResearchLogger.latinIME_handleBackspace(1);
+                    ResearchLogger.latinIME_handleBackspace(1, true /* shouldUncommitLogUnit */);
                 }
                 if (mDeleteCount > DELETE_ACCELERATE_AT) {
                     mConnection.deleteSurroundingText(1, 0);
                     if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                        ResearchLogger.latinIME_handleBackspace(1);
+                        ResearchLogger.latinIME_handleBackspace(1,
+                                true /* shouldUncommitLogUnit */);
                     }
                 }
             }
-            if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
                 restartSuggestionsOnWordBeforeCursorIfAtEndOfWord();
             }
         }
@@ -1900,8 +1976,9 @@
         }
         if ((SPACE_STATE_WEAK == spaceState || SPACE_STATE_SWAP_PUNCTUATION == spaceState)
                 && isFromSuggestionStrip) {
-            if (mSettings.getCurrent().isUsuallyPrecededBySpace(code)) return false;
-            if (mSettings.getCurrent().isUsuallyFollowedBySpace(code)) return true;
+            final SettingsValues currentSettings = mSettings.getCurrent();
+            if (currentSettings.isUsuallyPrecededBySpace(code)) return false;
+            if (currentSettings.isUsuallyFollowedBySpace(code)) return true;
             mConnection.removeTrailingSpace();
         }
         return false;
@@ -1913,8 +1990,8 @@
 
         // TODO: remove isWordConnector() and use isUsuallyFollowedBySpace() instead.
         // See onStartBatchInput() to see how to do it.
-        if (SPACE_STATE_PHANTOM == spaceState &&
-                !mSettings.getCurrent().isWordConnector(primaryCode)) {
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (SPACE_STATE_PHANTOM == spaceState && !currentSettings.isWordConnector(primaryCode)) {
             if (isComposingWord) {
                 // Sanity check
                 throw new RuntimeException("Should not be composing here");
@@ -1932,9 +2009,9 @@
         // dozen milliseconds. Avoid calling it as much as possible, since we are on the UI
         // thread here.
         if (!isComposingWord && (isAlphabet(primaryCode)
-                || mSettings.getCurrent().isWordConnector(primaryCode))
-                && mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation) &&
-                !mConnection.isCursorTouchingWord(mSettings.getCurrent())) {
+                || currentSettings.isWordConnector(primaryCode))
+                && currentSettings.isSuggestionsRequested(mDisplayOrientation) &&
+                !mConnection.isCursorTouchingWord(currentSettings)) {
             // Reset entirely the composing state anyway, then start composing a new word unless
             // the character is a single quote. The idea here is, single quote is not a
             // separator and it should be treated as a normal character, except in the first
@@ -1977,8 +2054,8 @@
             if (null != mSuggestionStripView) mSuggestionStripView.dismissAddToDictionaryHint();
         }
         mHandler.postUpdateSuggestionStrip();
-        if (mSettings.isInternal()) {
-            Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+        if (currentSettings.mIsInternal) {
+            LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
         }
     }
 
@@ -1990,9 +2067,10 @@
             final CharSequence selectedText =
                     mConnection.getSelectedText(0 /* flags, 0 for no styles */);
             if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection
+            final SettingsValues currentSettings = mSettings.getCurrent();
             mRecapitalizeStatus.initialize(mLastSelectionStart, mLastSelectionEnd,
-                    selectedText.toString(), mSettings.getCurrentLocale(),
-                    mSettings.getWordSeparators());
+                    selectedText.toString(), currentSettings.mLocale,
+                    currentSettings.mWordSeparators);
             // We trim leading and trailing whitespace.
             mRecapitalizeStatus.trim();
             // Trimming the object may have changed the length of the string, and we need to
@@ -2020,17 +2098,15 @@
     // Returns true if we did an autocorrection, false otherwise.
     private boolean handleSeparator(final int primaryCode, final int x, final int y,
             final int spaceState) {
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
-        }
         boolean didAutoCorrect = false;
         if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) {
             // If we are in the middle of a recorrection, we need to commit the recorrection
             // first so that we can insert the separator at the current cursor position.
             resetEntireInputState(mLastSelectionStart);
         }
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (mWordComposer.isComposingWord()) {
-            if (mSettings.getCurrent().mCorrectionEnabled) {
+            if (currentSettings.mCorrectionEnabled) {
                 // TODO: maybe cache Strings in an <String> sparse array or something
                 commitCurrentAutoCorrection(new String(new int[]{primaryCode}, 0, 1));
                 didAutoCorrect = true;
@@ -2043,13 +2119,16 @@
                 Constants.SUGGESTION_STRIP_COORDINATE == x);
 
         if (SPACE_STATE_PHANTOM == spaceState &&
-                mSettings.getCurrent().isUsuallyPrecededBySpace(primaryCode)) {
+                currentSettings.isUsuallyPrecededBySpace(primaryCode)) {
             promotePhantomSpace();
         }
+        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+            ResearchLogger.latinIME_handleSeparator(primaryCode, mWordComposer.isComposingWord());
+        }
         sendKeyCodePoint(primaryCode);
 
         if (Constants.CODE_SPACE == primaryCode) {
-            if (mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+            if (currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
                 if (maybeDoubleSpacePeriod()) {
                     mSpaceState = SPACE_STATE_DOUBLE;
                 } else if (!isShowingPunctuationList()) {
@@ -2064,7 +2143,7 @@
                 swapSwapperAndSpace();
                 mSpaceState = SPACE_STATE_SWAP_PUNCTUATION;
             } else if (SPACE_STATE_PHANTOM == spaceState
-                    && mSettings.getCurrent().isUsuallyFollowedBySpace(primaryCode)) {
+                    && currentSettings.isUsuallyFollowedBySpace(primaryCode)) {
                 // If we are in phantom space state, and the user presses a separator, we want to
                 // stay in phantom space state so that the next keypress has a chance to add the
                 // space. For example, if I type "Good dat", pick "day" from the suggestion strip
@@ -2082,8 +2161,8 @@
             // already displayed or not, so it's okay.
             setPunctuationSuggestions();
         }
-        if (mSettings.isInternal()) {
-            Utils.Stats.onSeparator((char)primaryCode, x, y);
+        if (currentSettings.mIsInternal) {
+            LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
         }
 
         mKeyboardSwitcher.updateShiftState();
@@ -2115,17 +2194,18 @@
     }
 
     private boolean isSuggestionsStripVisible() {
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (mSuggestionStripView == null)
             return false;
         if (mSuggestionStripView.isShowingAddToDictionaryHint())
             return true;
-        if (null == mSettings.getCurrent())
+        if (null == currentSettings)
             return false;
-        if (!mSettings.getCurrent().isSuggestionStripVisibleInOrientation(mDisplayOrientation))
+        if (!currentSettings.isSuggestionStripVisibleInOrientation(mDisplayOrientation))
             return false;
-        if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn())
+        if (currentSettings.isApplicationSpecifiedCompletionsOn())
             return true;
-        return mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation);
+        return currentSettings.isSuggestionsRequested(mDisplayOrientation);
     }
 
     private void clearSuggestionStrip() {
@@ -2158,10 +2238,11 @@
 
     private void updateSuggestionStrip() {
         mHandler.cancelUpdateSuggestionStrip();
+        final SettingsValues currentSettings = mSettings.getCurrent();
 
         // Check if we have a suggestion engine attached.
         if (mSuggest == null
-                || !mSettings.getCurrent().isSuggestionsRequested(mDisplayOrientation)) {
+                || !currentSettings.isSuggestionsRequested(mDisplayOrientation)) {
             if (mWordComposer.isComposingWord()) {
                 Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
                         + "requested!");
@@ -2169,7 +2250,7 @@
             return;
         }
 
-        if (!mWordComposer.isComposingWord() && !mSettings.getCurrent().mBigramPredictionEnabled) {
+        if (!mWordComposer.isComposingWord() && !currentSettings.mBigramPredictionEnabled) {
             setPunctuationSuggestions();
             return;
         }
@@ -2182,19 +2263,21 @@
 
     private SuggestedWords getSuggestedWords(final int sessionId) {
         final Keyboard keyboard = mKeyboardSwitcher.getKeyboard();
-        if (keyboard == null || mSuggest == null) {
+        final Suggest suggest = mSuggest;
+        if (keyboard == null || suggest == null) {
             return SuggestedWords.EMPTY;
         }
         // Get the word on which we should search the bigrams. If we are composing a word, it's
         // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we
         // should just skip whitespace if any, so 1.
         // TODO: this is slow (2-way IPC) - we should probably cache this instead.
+        final SettingsValues currentSettings = mSettings.getCurrent();
         final String prevWord =
-                mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators,
+                mConnection.getNthPreviousWord(currentSettings.mWordSeparators,
                 mWordComposer.isComposingWord() ? 2 : 1);
-        return mSuggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
-                mSettings.getBlockPotentiallyOffensive(),
-                mSettings.getCurrent().mCorrectionEnabled, sessionId);
+        return suggest.getSuggestedWords(mWordComposer, prevWord, keyboard.getProximityInfo(),
+                currentSettings.mBlockPotentiallyOffensive,
+                currentSettings.mCorrectionEnabled, sessionId);
     }
 
     private SuggestedWords getSuggestedWordsOrOlderSuggestions(final int sessionId) {
@@ -2272,7 +2355,8 @@
                         + "is empty? Impossible! I must commit suicide.");
             }
             if (mSettings.isInternal()) {
-                Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
+                LatinImeLoggerUtils.onAutoCorrection(
+                        typedWord, autoCorrection, separatorString, mWordComposer);
             }
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 final SuggestedWords suggestedWords = mSuggestedWords;
@@ -2319,18 +2403,19 @@
         }
 
         mConnection.beginBatchEdit();
+        final SettingsValues currentSettings = mSettings.getCurrent();
         if (SPACE_STATE_PHANTOM == mSpaceState && suggestion.length() > 0
                 // In the batch input mode, a manually picked suggested word should just replace
                 // the current batch input text and there is no need for a phantom space.
                 && !mWordComposer.isBatchMode()) {
             final int firstChar = Character.codePointAt(suggestion, 0);
-            if (!mSettings.getCurrent().isWordSeparator(firstChar)
-                    || mSettings.getCurrent().isUsuallyPrecededBySpace(firstChar)) {
+            if (!currentSettings.isWordSeparator(firstChar)
+                    || currentSettings.isUsuallyPrecededBySpace(firstChar)) {
                 promotePhantomSpace();
             }
         }
 
-        if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()
+        if (currentSettings.isApplicationSpecifiedCompletionsOn()
                 && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
             mSuggestedWords = SuggestedWords.EMPTY;
@@ -2354,7 +2439,8 @@
                 LastComposedWord.NOT_A_SEPARATOR);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion,
-                    mWordComposer.isBatchMode());
+                    mWordComposer.isBatchMode(), suggestionInfo.mScore, suggestionInfo.mKind,
+                    suggestionInfo.mSourceDict);
         }
         mConnection.endBatchEdit();
         // Don't allow cancellation of manual pick
@@ -2367,18 +2453,21 @@
         // 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 Suggest suggest = mSuggest;
         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);
+                (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind
+                        || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind)
+                        && suggest != null
+                        // If the suggestion is not in the dictionary, the hint should be shown.
+                        && !AutoCorrectionUtils.isValidWord(suggest, suggestion, true);
 
-        if (mSettings.isInternal()) {
-            Stats.onSeparator((char)Constants.CODE_SPACE,
+        if (currentSettings.mIsInternal) {
+            LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
             mSuggestionStripView.showAddToDictionaryHint(
-                    suggestion, mSettings.getCurrent().mHintToSaveText);
+                    suggestion, currentSettings.mHintToSaveText);
         } else {
             // If we're not showing the "Touch again to save", then update the suggestion strip.
             mHandler.postUpdateSuggestionStrip();
@@ -2404,10 +2493,11 @@
     }
 
     private void setPunctuationSuggestions() {
-        if (mSettings.getCurrent().mBigramPredictionEnabled) {
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (currentSettings.mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(currentSettings.mSuggestPuncList, false);
         }
         setAutoCorrectionIndicator(false);
         setSuggestionStripShown(isSuggestionsStripVisible());
@@ -2415,21 +2505,20 @@
 
     private String addToUserHistoryDictionary(final String suggestion) {
         if (TextUtils.isEmpty(suggestion)) return null;
-        if (mSuggest == null) return null;
+        final Suggest suggest = mSuggest;
+        if (suggest == null) return null;
 
         // If correction is not enabled, we don't add words to the user history dictionary.
         // That's to avoid unintended additions in some sensitive fields, or fields that
         // expect to receive non-words.
-        if (!mSettings.getCurrent().mCorrectionEnabled) return null;
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (!currentSettings.mCorrectionEnabled) return null;
 
-        final Suggest suggest = mSuggest;
-        final UserHistoryDictionary userHistoryDictionary = mUserHistoryDictionary;
-        if (suggest == null || userHistoryDictionary == null) {
-            // Avoid concurrent issue
-            return null;
-        }
-        final String prevWord
-                = mConnection.getNthPreviousWord(mSettings.getCurrent().mWordSeparators, 2);
+        final UserHistoryPredictionDictionary userHistoryPredictionDictionary =
+                mUserHistoryPredictionDictionary;
+        if (userHistoryPredictionDictionary == null) return null;
+
+        final String prevWord = mConnection.getNthPreviousWord(currentSettings.mWordSeparators, 2);
         final String secondWord;
         if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) {
             secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale());
@@ -2438,10 +2527,10 @@
         }
         // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
         // We don't add words with 0-frequency (assuming they would be profanity etc.).
-        final int maxFreq = AutoCorrection.getMaxFrequency(
+        final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
                 suggest.getUnigramDictionaries(), suggestion);
         if (maxFreq == 0) return null;
-        userHistoryDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
+        userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
         return prevWord;
     }
 
@@ -2458,35 +2547,34 @@
         if (mLastSelectionStart != mLastSelectionEnd) return;
         // If we don't know the cursor location, return.
         if (mLastSelectionStart < 0) return;
-        if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
-        final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+        final SettingsValues currentSettings = mSettings.getCurrent();
+        if (!mConnection.isCursorTouchingWord(currentSettings)) return;
+        final TextRange range = mConnection.getWordRangeAtCursor(currentSettings.mWordSeparators,
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
         // longer than what the entire text is supposed to be, the safe thing to do is bail out.
-        if (range.mCharsBefore > mLastSelectionStart) return;
+        final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor();
+        if (numberOfCharsInWordBeforeCursor > mLastSelectionStart) return;
         final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
         final String typedWord = range.mWord.toString();
-        if (range.mWord instanceof SpannableString) {
-            final SpannableString spannableString = (SpannableString)range.mWord;
-            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));
-                    }
+        int i = 0;
+        for (final SuggestionSpan span : range.getSuggestionSpansAtWord()) {
+            for (final 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(typedWord, mKeyboardSwitcher.getKeyboard());
-        mWordComposer.setCursorPositionWithinWord(range.mCharsBefore);
-        mConnection.setComposingRegion(mLastSelectionStart - range.mCharsBefore,
-                mLastSelectionEnd + range.mCharsAfter);
+        // TODO: this is in chars but the callee expects code points!
+        mWordComposer.setCursorPositionWithinWord(numberOfCharsInWordBeforeCursor);
+        mConnection.setComposingRegion(
+                mLastSelectionStart - numberOfCharsInWordBeforeCursor,
+                mLastSelectionEnd + range.getNumberOfCharsInWordAfterCursor());
         final SuggestedWords suggestedWords;
         if (suggestions.isEmpty()) {
             // We come here if there weren't any suggestion spans on this word. We will try to
@@ -2575,18 +2663,16 @@
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
         if (!TextUtils.isEmpty(previousWord) && !TextUtils.isEmpty(committedWord)) {
-            mUserHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+            mUserHistoryPredictionDictionary.cancelAddingUserHistory(previousWord, committedWord);
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
         if (mSettings.isInternal()) {
-            Stats.onSeparator(mLastComposedWord.mSeparatorString,
+            LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.latinIME_revertCommit(committedWord, originallyTypedWord,
                     mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString);
-            ResearchLogger.getInstance().uncommitCurrentLogUnit(committedWord,
-                    true /* dumpCurrentLogUnit */);
         }
         // Don't restart suggestion yet. We'll restart if the user deletes the
         // separator.
@@ -2599,45 +2685,54 @@
     public void promotePhantomSpace() {
         if (mSettings.getCurrent().shouldInsertSpacesAutomatically()
                 && !mConnection.textBeforeCursorLooksLikeURL()) {
-            sendKeyCodePoint(Constants.CODE_SPACE);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 ResearchLogger.latinIME_promotePhantomSpace();
             }
+            sendKeyCodePoint(Constants.CODE_SPACE);
         }
     }
 
-    // Used by the RingCharBuffer
-    public boolean isWordSeparator(final int code) {
-        return mSettings.getCurrent().isWordSeparator(code);
-    }
-
     // TODO: Make this private
     // Outside LatinIME, only used by the {@link InputTestsBase} test suite.
     @UsedForTesting
     void loadKeyboard() {
-        // TODO: Why are we calling {@link #loadSettings()} and {@link #initSuggest()} in a
-        // different order than in {@link #onStartInputView}?
-        initSuggest();
+        // Since we are switching languages, the most urgent thing is to let the keyboard graphics
+        // update. LoadKeyboard does that, but we need to wait for buffer flip for it to be on
+        // the screen. Anything we do right now will delay this, so wait until the next frame
+        // before we do the rest, like reopening dictionaries and updating suggestions. So we
+        // post a message.
+        mHandler.postReopenDictionaries();
         loadSettings();
         if (mKeyboardSwitcher.getMainKeyboardView() != null) {
             // Reload keyboard because the current language has been changed.
             mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent());
         }
-        // Since we just changed languages, we should re-evaluate suggestions with whatever word
-        // we are currently composing. If we are not composing anything, we may want to display
-        // predictions or punctuation signs (which is done by the updateSuggestionStrip anyway).
-        mHandler.postUpdateSuggestionStrip();
     }
 
-    // Callback called by PointerTracker through the KeyboardActionListener. This is called when a
-    // key is depressed; release matching call is onReleaseKey below.
+    private void hapticAndAudioFeedback(final int code, final boolean isRepeatKey) {
+        final MainKeyboardView keyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        if (keyboardView != null && keyboardView.isInSlidingKeyInput()) {
+            // No need to feedback while sliding input.
+            return;
+        }
+        if (isRepeatKey && code == Constants.CODE_DELETE && !mConnection.canDeleteCharacters()) {
+            // No need to feedback when repeating delete key will have no effect.
+            return;
+        }
+        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, keyboardView);
+    }
+
+    // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
+    // release matching call is {@link #onReleaseKey(int,boolean)} below.
     @Override
-    public void onPressKey(final int primaryCode, final boolean isSinglePointer) {
+    public void onPressKey(final int primaryCode, final boolean isRepeatKey,
+            final boolean isSinglePointer) {
         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
+        hapticAndAudioFeedback(primaryCode, isRepeatKey);
     }
 
-    // Callback by PointerTracker through the KeyboardActionListener. This is called when a key
-    // is released; press matching call is onPressKey above.
+    // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
+    // press matching call is {@link #onPressKey(int,boolean,boolean)} above.
     @Override
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
@@ -2703,7 +2798,7 @@
             if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                 mSubtypeSwitcher.onNetworkStateChanged(intent);
             } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
-                mKeyboardSwitcher.onRingerModeChanged();
+                AudioAndHapticFeedbackManager.getInstance().onRingerModeChanged();
             }
         }
     };
@@ -2734,7 +2829,7 @@
         final CharSequence[] items = new CharSequence[] {
                 // TODO: Should use new string "Select active input modes".
                 getString(R.string.language_selection_title),
-                getString(Utils.getAcitivityTitleResId(this, SettingsActivity.class)),
+                getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)),
         };
         final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
             @Override
@@ -2787,6 +2882,18 @@
         return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
     }
 
+    // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
+    @UsedForTesting
+    /* package for test */ boolean isCurrentlyWaitingForMainDictionary() {
+        return mSuggest.isCurrentlyWaitingForMainDictionary();
+    }
+
+    // DO NOT USE THIS for any other purpose than testing. This is information private to LatinIME.
+    @UsedForTesting
+    /* package for test */ boolean hasMainDictionary() {
+        return mSuggest.hasMainDictionary();
+    }
+
     public void debugDumpStateAndCrashWithException(final String context) {
         final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString());
         s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 980215d..b69e3f8 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,7 +17,6 @@
 package com.android.inputmethod.latin;
 
 import android.inputmethodservice.InputMethodService;
-import android.text.SpannableString;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -28,6 +27,11 @@
 import android.view.inputmethod.InputConnection;
 
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.settings.SettingsValues;
+import com.android.inputmethod.latin.utils.CapsModeUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.Locale;
@@ -47,16 +51,19 @@
     private static final boolean DEBUG_PREVIOUS_TEXT = false;
     private static final boolean DEBUG_BATCH_NESTING = false;
     // Provision for a long word pair and a separator
-    private static final int LOOKBACK_CHARACTER_NUM = Constants.Dictionary.MAX_WORD_LENGTH * 2 + 1;
+    private static final int LOOKBACK_CHARACTER_NUM = Constants.DICTIONARY_MAX_WORD_LENGTH * 2 + 1;
     private static final Pattern spaceRegex = Pattern.compile("\\s+");
     private static final int INVALID_CURSOR_POSITION = -1;
 
     /**
-     * This variable contains the value LatinIME thinks the cursor position should be at now.
-     * This is a few steps in advance of what the TextView thinks it is, because TextView will
-     * only know after the IPC calls gets through.
+     * This variable contains an expected value for the cursor position. This is where the
+     * cursor may end up after all the keyboard-triggered updates have passed. We keep this to
+     * compare it to the actual cursor position to guess whether the move was caused by a
+     * keyboard command or not.
+     * It's not really the cursor position: the cursor may not be there yet, and it's also expected 
+     * there be cases where it never actually comes to be there.
      */
-    private int mCurrentCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
+    private int mExpectedCursorPosition = INVALID_CURSOR_POSITION; // in chars, not code points
     /**
      * 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.
@@ -97,16 +104,16 @@
         final String reference = (beforeCursor.length() <= actualLength) ? beforeCursor.toString()
                 : beforeCursor.subSequence(beforeCursor.length() - actualLength,
                         beforeCursor.length()).toString();
-        if (et.selectionStart != mCurrentCursorPosition
+        if (et.selectionStart != mExpectedCursorPosition
                 || !(reference.equals(internal.toString()))) {
-            final String context = "Expected cursor position = " + mCurrentCursorPosition
+            final String context = "Expected cursor position = " + mExpectedCursorPosition
                     + "\nActual cursor position = " + et.selectionStart
                     + "\nExpected text = " + internal.length() + " " + internal
                     + "\nActual text = " + reference.length() + " " + reference;
             ((LatinIME)mParent).debugDumpStateAndCrashWithException(context);
         } else {
-            Log.e(TAG, Utils.getStackTrace(2));
-            Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart);
+            Log.e(TAG, DebugLogUtils.getStackTrace(2));
+            Log.e(TAG, "Exp <> Actual : " + mExpectedCursorPosition + " <> " + et.selectionStart);
         }
     }
 
@@ -137,7 +144,7 @@
 
     public void resetCachesUponCursorMove(final int newCursorPosition,
             final boolean shouldFinishComposition) {
-        mCurrentCursorPosition = newCursorPosition;
+        mExpectedCursorPosition = newCursorPosition;
         mComposingText.setLength(0);
         mCommittedTextBeforeComposingText.setLength(0);
         final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0);
@@ -154,7 +161,7 @@
         if (mNestLevel != 1) {
             // TODO: exception instead
             Log.e(TAG, "Batch edit level incorrect : " + mNestLevel);
-            Log.e(TAG, Utils.getStackTrace(4));
+            Log.e(TAG, DebugLogUtils.getStackTrace(4));
         }
     }
 
@@ -162,7 +169,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(mComposingText);
-        mCurrentCursorPosition += mComposingText.length();
+        mExpectedCursorPosition += mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.finishComposingText();
@@ -176,7 +183,7 @@
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         mCommittedTextBeforeComposingText.append(text);
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitText(text, i);
@@ -188,6 +195,10 @@
         return mIC.getSelectedText(flags);
     }
 
+    public boolean canDeleteCharacters() {
+        return mExpectedCursorPosition > 0;
+    }
+
     /**
      * Gets the caps modes we should be in after this specific string.
      *
@@ -222,7 +233,7 @@
         // heavy pressing of delete, for example DEFAULT_TEXT_CACHE_SIZE - 5 times or so.
         // getCapsMode should be updated to be able to return a "not enough info" result so that
         // we can get more context only when needed.
-        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mCurrentCursorPosition) {
+        if (TextUtils.isEmpty(mCommittedTextBeforeComposingText) && 0 != mExpectedCursorPosition) {
             mCommittedTextBeforeComposingText.append(
                     getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
         }
@@ -238,22 +249,35 @@
                 mCommittedTextBeforeComposingText.length());
     }
 
-    public CharSequence getTextBeforeCursor(final int i, final int j) {
-        // TODO: use mCommittedTextBeforeComposingText if possible to improve performance
+    public CharSequence getTextBeforeCursor(final int n, final int flags) {
+        final int cachedLength =
+                mCommittedTextBeforeComposingText.length() + mComposingText.length();
+        // If we have enough characters to satisfy the request, or if we have all characters in
+        // the text field, then we can return the cached version right away.
+        if (cachedLength >= n || cachedLength >= mExpectedCursorPosition) {
+            final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText);
+            s.append(mComposingText);
+            if (s.length() > n) {
+                s.delete(0, s.length() - n);
+            }
+            return s;
+        }
         mIC = mParent.getCurrentInputConnection();
-        if (null != mIC) return mIC.getTextBeforeCursor(i, j);
+        if (null != mIC) {
+            return mIC.getTextBeforeCursor(n, flags);
+        }
         return null;
     }
 
-    public CharSequence getTextAfterCursor(final int i, final int j) {
+    public CharSequence getTextAfterCursor(final int n, final int flags) {
         mIC = mParent.getCurrentInputConnection();
-        if (null != mIC) return mIC.getTextAfterCursor(i, j);
+        if (null != mIC) return mIC.getTextAfterCursor(n, flags);
         return null;
     }
 
-    public void deleteSurroundingText(final int i, final int j) {
+    public void deleteSurroundingText(final int beforeLength, final int afterLength) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
-        final int remainingChars = mComposingText.length() - i;
+        final int remainingChars = mComposingText.length() - beforeLength;
         if (remainingChars >= 0) {
             mComposingText.setLength(remainingChars);
         } else {
@@ -263,15 +287,15 @@
                     + remainingChars, 0);
             mCommittedTextBeforeComposingText.setLength(len);
         }
-        if (mCurrentCursorPosition > i) {
-            mCurrentCursorPosition -= i;
+        if (mExpectedCursorPosition > beforeLength) {
+            mExpectedCursorPosition -= beforeLength;
         } else {
-            mCurrentCursorPosition = 0;
+            mExpectedCursorPosition = 0;
         }
         if (null != mIC) {
-            mIC.deleteSurroundingText(i, j);
+            mIC.deleteSurroundingText(beforeLength, afterLength);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.richInputConnection_deleteSurroundingText(i, j);
+                ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength);
             }
         }
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
@@ -300,7 +324,7 @@
             switch (keyEvent.getKeyCode()) {
             case KeyEvent.KEYCODE_ENTER:
                 mCommittedTextBeforeComposingText.append("\n");
-                mCurrentCursorPosition += 1;
+                mExpectedCursorPosition += 1;
                 break;
             case KeyEvent.KEYCODE_DEL:
                 if (0 == mComposingText.length()) {
@@ -312,18 +336,18 @@
                 } else {
                     mComposingText.delete(mComposingText.length() - 1, mComposingText.length());
                 }
-                if (mCurrentCursorPosition > 0) mCurrentCursorPosition -= 1;
+                if (mExpectedCursorPosition > 0) mExpectedCursorPosition -= 1;
                 break;
             case KeyEvent.KEYCODE_UNKNOWN:
                 if (null != keyEvent.getCharacters()) {
                     mCommittedTextBeforeComposingText.append(keyEvent.getCharacters());
-                    mCurrentCursorPosition += keyEvent.getCharacters().length();
+                    mExpectedCursorPosition += keyEvent.getCharacters().length();
                 }
                 break;
             default:
                 final String text = new String(new int[] { keyEvent.getUnicodeChar() }, 0, 1);
                 mCommittedTextBeforeComposingText.append(text);
-                mCurrentCursorPosition += text.length();
+                mExpectedCursorPosition += text.length();
                 break;
             }
         }
@@ -338,7 +362,6 @@
     public void setComposingRegion(final int start, final int end) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
-        mCurrentCursorPosition = end;
         final CharSequence textBeforeCursor =
                 getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
         mCommittedTextBeforeComposingText.setLength(0);
@@ -355,32 +378,32 @@
         }
     }
 
-    public void setComposingText(final CharSequence text, final int i) {
+    public void setComposingText(final CharSequence text, final int newCursorPosition) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
-        mCurrentCursorPosition += text.length() - mComposingText.length();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         mComposingText.append(text);
         // TODO: support values of i != 1. At this time, this is never called with i != 1.
         if (null != mIC) {
-            mIC.setComposingText(text, i);
+            mIC.setComposingText(text, newCursorPosition);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.richInputConnection_setComposingText(text, i);
+                ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition);
             }
         }
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
     }
 
-    public void setSelection(final int from, final int to) {
+    public void setSelection(final int start, final int end) {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
         if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug();
         if (null != mIC) {
-            mIC.setSelection(from, to);
+            mIC.setSelection(start, end);
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-                ResearchLogger.richInputConnection_setSelection(from, to);
+                ResearchLogger.richInputConnection_setSelection(start, end);
             }
         }
-        mCurrentCursorPosition = from;
+        mExpectedCursorPosition = start;
         mCommittedTextBeforeComposingText.setLength(0);
         mCommittedTextBeforeComposingText.append(getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0));
     }
@@ -403,7 +426,7 @@
         // 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();
+        mExpectedCursorPosition += text.length() - mComposingText.length();
         mComposingText.setLength(0);
         if (null != mIC) {
             mIC.commitCompletion(completionInfo);
@@ -418,7 +441,7 @@
     public String getNthPreviousWord(final String sentenceSeperators, final int n) {
         mIC = mParent.getCurrentInputConnection();
         if (null == mIC) return null;
-        final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
+        final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0);
         if (DEBUG_PREVIOUS_TEXT && null != prev) {
             final int checkLength = LOOKBACK_CHARACTER_NUM - 1;
             final String reference = prev.length() <= checkLength ? prev.toString()
@@ -437,32 +460,6 @@
         return getNthPreviousWord(prev, sentenceSeperators, n);
     }
 
-    /**
-     * Represents a range of text, relative to the current cursor position.
-     */
-    public static final class Range {
-        /** Characters before selection start */
-        public final int mCharsBefore;
-
-        /**
-         * Characters after selection start, including one trailing word
-         * separator.
-         */
-        public final int mCharsAfter;
-
-        /** The actual characters that make up a word */
-        public final CharSequence mWord;
-
-        public Range(int charsBefore, int charsAfter, CharSequence word) {
-            if (charsBefore < 0 || charsAfter < 0) {
-                throw new IndexOutOfBoundsException();
-            }
-            this.mCharsBefore = charsBefore;
-            this.mCharsAfter = charsAfter;
-            this.mWord = word;
-        }
-    }
-
     private static boolean isSeparator(int code, String sep) {
         return sep.indexOf(code) != -1;
     }
@@ -509,7 +506,7 @@
      */
     public CharSequence getWordAtCursor(String separators) {
         // getWordRangeAtCursor returns null if the connection is null
-        Range r = getWordRangeAtCursor(separators, 0);
+        TextRange r = getWordRangeAtCursor(separators, 0);
         return (r == null) ? null : r.mWord;
     }
 
@@ -521,7 +518,8 @@
      *   be included in the returned range
      * @return a range containing the text surrounding the cursor
      */
-    public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
+    public TextRange getWordRangeAtCursor(final String sep,
+            final int additionalPrecedingWordsCount) {
         mIC = mParent.getCurrentInputConnection();
         if (mIC == null || sep == null) {
             return null;
@@ -571,19 +569,18 @@
             }
         }
 
-        final SpannableString word = new SpannableString(TextUtils.concat(
-                before.subSequence(startIndexInBefore, before.length()),
-                after.subSequence(0, endIndexInAfter)));
-        return new Range(before.length() - startIndexInBefore, endIndexInAfter, word);
+        return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
+                before.length() + endIndexInAfter, before.length());
     }
 
     public boolean isCursorTouchingWord(final SettingsValues settingsValues) {
-        final CharSequence before = getTextBeforeCursor(1, 0);
-        final CharSequence after = getTextAfterCursor(1, 0);
-        if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0))
-                && !settingsValues.isWordConnector(before.charAt(0))) {
+        final int codePointBeforeCursor = getCodePointBeforeCursor();
+        if (Constants.NOT_A_CODE != codePointBeforeCursor
+                && !settingsValues.isWordSeparator(codePointBeforeCursor)
+                && !settingsValues.isWordConnector(codePointBeforeCursor)) {
             return true;
         }
+        final CharSequence after = getTextAfterCursor(1, 0);
         if (!TextUtils.isEmpty(after) && !settingsValues.isWordSeparator(after.charAt(0))
                 && !settingsValues.isWordConnector(after.charAt(0))) {
             return true;
@@ -593,9 +590,8 @@
 
     public void removeTrailingSpace() {
         if (DEBUG_BATCH_NESTING) checkBatchEdit();
-        final CharSequence lastOne = getTextBeforeCursor(1, 0);
-        if (lastOne != null && lastOne.length() == 1
-                && lastOne.charAt(0) == Constants.CODE_SPACE) {
+        final int codePointBeforeCursor = getCodePointBeforeCursor();
+        if (Constants.CODE_SPACE == codePointBeforeCursor) {
             deleteSurroundingText(1, 0);
         }
     }
@@ -650,7 +646,7 @@
         // be needed, but it's there just in case something went wrong.
         final CharSequence textBeforeCursor = getTextBeforeCursor(2, 0);
         final String periodSpace = ". ";
-        if (!periodSpace.equals(textBeforeCursor)) {
+        if (!TextUtils.equals(periodSpace, textBeforeCursor)) {
             // Theoretically we should not be coming here if there isn't ". " before the
             // cursor, but the application may be changing the text while we are typing, so
             // anything goes. We should not crash.
@@ -712,14 +708,14 @@
      */
     public boolean isBelatedExpectedUpdate(final int oldSelStart, final int newSelStart) {
         // If this is an update that arrives at our expected position, it's a belated update.
-        if (newSelStart == mCurrentCursorPosition) return true;
+        if (newSelStart == mExpectedCursorPosition) return true;
         // If this is an update that moves the cursor from our expected position, it must be
         // an explicit move.
-        if (oldSelStart == mCurrentCursorPosition) return false;
+        if (oldSelStart == mExpectedCursorPosition) return false;
         // The following returns true if newSelStart is between oldSelStart and
         // mCurrentCursorPosition. We assume that if the updated position is between the old
         // position and the expected position, then it must be a belated update.
-        return (newSelStart - oldSelStart) * (mCurrentCursorPosition - newSelStart) >= 0;
+        return (newSelStart - oldSelStart) * (mExpectedCursorPosition - newSelStart) >= 0;
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 0dd302a..6b6bbf3 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -28,8 +28,13 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.compat.InputMethodManagerCompatWrapper;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -46,6 +51,10 @@
 
     private InputMethodManagerCompatWrapper mImmWrapper;
     private InputMethodInfo mInputMethodInfoOfThisIme;
+    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+            mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
+    final HashMap<InputMethodInfo, List<InputMethodSubtype>>
+            mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap();
 
     private static final int INDEX_NOT_FOUND = -1;
 
@@ -54,13 +63,6 @@
         return sInstance;
     }
 
-    // Caveat: This may cause IPC
-    public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) {
-        // Basically called to check whether this IME has been triggered by the current user or not
-        return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)).
-                getInputMethodList().isEmpty();
-    }
-
     public static void init(final Context context) {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         sInstance.initInternal(context, prefs);
@@ -84,11 +86,11 @@
         mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context);
 
         // Initialize additional subtypes.
-        SubtypeLocale.init(context);
+        SubtypeLocaleUtils.init(context);
         final String prefAdditionalSubtypes = Settings.readPrefAdditionalSubtypes(
                 prefs, context.getResources());
         final InputMethodSubtype[] additionalSubtypes =
-                AdditionalSubtype.createAdditionalSubtypesArray(prefAdditionalSubtypes);
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefAdditionalSubtypes);
         setAdditionalInputMethodSubtypes(additionalSubtypes);
     }
 
@@ -109,8 +111,8 @@
 
     public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList(
             boolean allowsImplicitlySelectedSubtypes) {
-        return mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
-                mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes);
+        return getEnabledInputMethodSubtypeList(mInputMethodInfoOfThisIme,
+                allowsImplicitlySelectedSubtypes);
     }
 
     public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) {
@@ -134,7 +136,7 @@
         final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes);
         if (currentIndex == INDEX_NOT_FOUND) {
             Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype="
-                    + SubtypeLocale.getSubtypeDisplayName(currentSubtype));
+                    + SubtypeLocaleUtils.getSubtypeNameForLogging(currentSubtype));
             return false;
         }
         final int nextIndex = (currentIndex + 1) % enabledSubtypes.size();
@@ -158,8 +160,8 @@
             return false;
         }
         final InputMethodInfo nextImi = getNextNonAuxiliaryIme(currentIndex, enabledImis);
-        final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList(
-                nextImi, true /* allowsImplicitlySelectedSubtypes */);
+        final List<InputMethodSubtype> enabledSubtypes = getEnabledInputMethodSubtypeList(nextImi,
+                true /* allowsImplicitlySelectedSubtypes */);
         if (enabledSubtypes.isEmpty()) {
             // The next IME has no subtype.
             imm.setInputMethod(token, nextImi.getId());
@@ -234,9 +236,8 @@
 
     public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi,
             final InputMethodSubtype subtype) {
-        return checkIfSubtypeBelongsToList(
-                subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
-                        imi, true /* allowsImplicitlySelectedSubtypes */));
+        return checkIfSubtypeBelongsToList(subtype, getEnabledInputMethodSubtypeList(imi,
+                true /* allowsImplicitlySelectedSubtypes */));
     }
 
     private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype,
@@ -297,8 +298,7 @@
         for (InputMethodInfo imi : imiList) {
             // We can return true immediately after we find two or more filtered IMEs.
             if (filteredImisCount > 1) return true;
-            final List<InputMethodSubtype> subtypes =
-                    mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true);
+            final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(imi, true);
             // IMEs that have no subtypes should be counted.
             if (subtypes.isEmpty()) {
                 ++filteredImisCount;
@@ -344,7 +344,7 @@
         final int count = myImi.getSubtypeCount();
         for (int i = 0; i < count; i++) {
             final InputMethodSubtype subtype = myImi.getSubtypeAt(i);
-            final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+            final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
             if (localeString.equals(subtype.getLocale())
                     && keyboardLayoutSetName.equals(layoutName)) {
                 return subtype;
@@ -361,5 +361,26 @@
     public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) {
         mImmWrapper.mImm.setAdditionalInputMethodSubtypes(
                 mInputMethodInfoOfThisIme.getId(), subtypes);
+        // Clear the cache so that we go read the subtypes again next time.
+        clearSubtypeCaches();
+    }
+
+    private List<InputMethodSubtype> getEnabledInputMethodSubtypeList(final InputMethodInfo imi,
+            final boolean allowsImplicitlySelectedSubtypes) {
+        final HashMap<InputMethodInfo, List<InputMethodSubtype>> cache =
+                allowsImplicitlySelectedSubtypes
+                ? mSubtypeListCacheWithImplicitlySelectedSubtypes
+                : mSubtypeListCacheWithoutImplicitlySelectedSubtypes;
+        final List<InputMethodSubtype> cachedList = cache.get(imi);
+        if (null != cachedList) return cachedList;
+        final List<InputMethodSubtype> result = mImmWrapper.mImm.getEnabledInputMethodSubtypeList(
+                imi, allowsImplicitlySelectedSubtypes);
+        cache.put(imi, result);
+        return result;
+    }
+
+    public void clearSubtypeCaches() {
+        mSubtypeListCacheWithImplicitlySelectedSubtypes.clear();
+        mSubtypeListCacheWithoutImplicitlySelectedSubtypes.clear();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index 282b579..be03d4a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -33,6 +33,7 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.List;
 import java.util.Locale;
@@ -43,20 +44,23 @@
     private static final String TAG = SubtypeSwitcher.class.getSimpleName();
 
     private static final SubtypeSwitcher sInstance = new SubtypeSwitcher();
+
     private /* final */ RichInputMethodManager mRichImm;
     private /* final */ Resources mResources;
     private /* final */ ConnectivityManager mConnectivityManager;
 
-    /*-----------------------------------------------------------*/
-    // Variants which should be changed only by reload functions.
-    private NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
+    private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage();
     private InputMethodInfo mShortcutInputMethodInfo;
     private InputMethodSubtype mShortcutSubtype;
     private InputMethodSubtype mNoLanguageSubtype;
-    /*-----------------------------------------------------------*/
-
     private boolean mIsNetworkConnected;
 
+    // Dummy no language QWERTY subtype. See {@link R.xml.method}.
+    private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype(
+            R.string.subtype_no_language_qwerty, R.drawable.ic_subtype_keyboard, "zz", "keyboard",
+            "KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable",
+            false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */);
+
     static final class NeedsToDisplayLanguage {
         private int mEnabledSubtypeCount;
         private boolean mIsSystemLanguageSameAsInputLanguage;
@@ -79,7 +83,7 @@
     }
 
     public static void init(final Context context) {
-        SubtypeLocale.init(context);
+        SubtypeLocaleUtils.init(context);
         RichInputMethodManager.init(context);
         sInstance.initialize(context);
     }
@@ -96,11 +100,6 @@
         mRichImm = RichInputMethodManager.getInstance();
         mConnectivityManager = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
-        mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY);
-        if (mNoLanguageSubtype == null) {
-            throw new RuntimeException("Can't find no lanugage with QWERTY subtype");
-        }
 
         final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo();
         mIsNetworkConnected = (info != null && info.isConnected());
@@ -155,10 +154,11 @@
     // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function.
     public void onSubtypeChanged(final InputMethodSubtype newSubtype) {
         if (DBG) {
-            Log.w(TAG, "onSubtypeChanged: " + SubtypeLocale.getSubtypeDisplayName(newSubtype));
+            Log.w(TAG, "onSubtypeChanged: "
+                    + SubtypeLocaleUtils.getSubtypeNameForLogging(newSubtype));
         }
 
-        final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype);
+        final Locale newLocale = SubtypeLocaleUtils.getSubtypeLocale(newSubtype);
         final Locale systemLocale = mResources.getConfiguration().locale;
         final boolean sameLocale = systemLocale.equals(newLocale);
         final boolean sameLanguage = systemLocale.getLanguage().equals(newLocale.getLanguage());
@@ -234,7 +234,7 @@
     //////////////////////////////////
 
     public boolean needsToDisplayLanguage(final Locale keyboardLocale) {
-        if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) {
+        if (keyboardLocale.toString().equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
             return true;
         }
         if (!keyboardLocale.equals(getCurrentSubtypeLocale())) {
@@ -251,14 +251,24 @@
 
     public Locale getCurrentSubtypeLocale() {
         if (null != sForcedLocaleForTesting) return sForcedLocaleForTesting;
-        return SubtypeLocale.getSubtypeLocale(getCurrentSubtype());
+        return SubtypeLocaleUtils.getSubtypeLocale(getCurrentSubtype());
     }
 
     public InputMethodSubtype getCurrentSubtype() {
-        return mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype);
+        return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype());
     }
 
     public InputMethodSubtype getNoLanguageSubtype() {
-        return mNoLanguageSubtype;
+        if (mNoLanguageSubtype == null) {
+            mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                    SubtypeLocaleUtils.NO_LANGUAGE, SubtypeLocaleUtils.QWERTY);
+        }
+        if (mNoLanguageSubtype != null) {
+            return mNoLanguageSubtype;
+        }
+        Log.w(TAG, "Can't find no lanugage with QWERTY subtype");
+        Log.w(TAG, "No input method subtype found; return dummy subtype: "
+                + DUMMY_NO_LANGUAGE_SUBTYPE);
+        return DUMMY_NO_LANGUAGE_SUBTYPE;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index dc9bef2..c2fdcb5 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -17,13 +17,21 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
+import android.preference.PreferenceManager;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.personalization.PersonalizationPredictionDictionary;
+import com.android.inputmethod.latin.personalization.UserHistoryPredictionDictionary;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.BoundedTreeSet;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -50,21 +58,22 @@
     // Close to -2**31
     private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000;
 
+    public static final int MAX_SUGGESTIONS = 18;
+
     public interface SuggestInitializationListener {
         public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable);
     }
 
     private static final boolean DBG = LatinImeLogger.sDBG;
 
-    private Dictionary mMainDictionary;
-    private ContactsBinaryDictionary mContactsDict;
     private final ConcurrentHashMap<String, Dictionary> mDictionaries =
             CollectionUtils.newConcurrentHashMap();
+    private HashSet<String> mOnlyDictionarySetForDebug = null;
+    private Dictionary mMainDictionary;
+    private ContactsBinaryDictionary mContactsDict;
     @UsedForTesting
     private boolean mIsCurrentlyWaitingForMainDictionary = false;
 
-    public static final int MAX_SUGGESTIONS = 18;
-
     private float mAutoCorrectionThreshold;
 
     // Locale used for upper- and title-casing words
@@ -74,15 +83,22 @@
             final SuggestInitializationListener listener) {
         initAsynchronously(context, locale, listener);
         mLocale = locale;
+        // initialize a debug flag for the personalization
+        if (Settings.readUseOnlyPersonalizationDictionaryForDebug(
+                PreferenceManager.getDefaultSharedPreferences(context))) {
+            mOnlyDictionarySetForDebug = new HashSet<String>();
+            mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION);
+            mOnlyDictionarySetForDebug.add(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+        }
     }
 
     @UsedForTesting
-    Suggest(final File dictionary, final long startOffset, final long length, final Locale locale) {
-        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionary,
-                startOffset, length /* useFullEditDistance */, false, locale);
+    Suggest(final AssetFileAddress[] dictionaryList, final Locale locale) {
+        final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(dictionaryList,
+                false /* useFullEditDistance */, locale);
         mLocale = locale;
         mMainDictionary = mainDict;
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, mainDict);
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, mainDict);
     }
 
     private void initAsynchronously(final Context context, final Locale locale,
@@ -90,6 +106,14 @@
         resetMainDict(context, locale, listener);
     }
 
+    private void addOrReplaceDictionaryInternal(final String key, final Dictionary dict) {
+        if (mOnlyDictionarySetForDebug != null && mOnlyDictionarySetForDebug.contains(key)) {
+            Log.w(TAG, "Ignore add " + key + " dictionary for debug.");
+            return;
+        }
+        addOrReplaceDictionary(mDictionaries, key, dict);
+    }
+
     private static void addOrReplaceDictionary(
             final ConcurrentHashMap<String, Dictionary> dictionaries,
             final String key, final Dictionary dict) {
@@ -113,7 +137,7 @@
             public void run() {
                 final DictionaryCollection newMainDict =
                         DictionaryFactory.createMainDictionaryFromManager(context, locale);
-                addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_MAIN, newMainDict);
+                addOrReplaceDictionaryInternal(Dictionary.TYPE_MAIN, newMainDict);
                 mMainDictionary = newMainDict;
                 if (listener != null) {
                     listener.onUpdateMainDictionaryAvailability(hasMainDictionary());
@@ -151,7 +175,7 @@
      * before the main dictionary, if set. This refers to the system-managed user dictionary.
      */
     public void setUserDictionary(final UserBinaryDictionary userDictionary) {
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary);
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_USER, userDictionary);
     }
 
     /**
@@ -161,11 +185,19 @@
      */
     public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) {
         mContactsDict = contactsDictionary;
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary);
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_CONTACTS, contactsDictionary);
     }
 
-    public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) {
-        addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary);
+    public void setUserHistoryPredictionDictionary(
+            final UserHistoryPredictionDictionary userHistoryPredictionDictionary) {
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_USER_HISTORY,
+                userHistoryPredictionDictionary);
+    }
+
+    public void setPersonalizationPredictionDictionary(
+            final PersonalizationPredictionDictionary personalizationPredictionDictionary) {
+        addOrReplaceDictionaryInternal(Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA,
+                personalizationPredictionDictionary);
     }
 
     public void setAutoCorrectionThreshold(float threshold) {
@@ -229,7 +261,7 @@
         // or if it's a 2+ characters non-word (i.e. it's not in the dictionary).
         final boolean allowsToBeAutoCorrected = (null != whitelistedWord
                 && !whitelistedWord.equals(consideredWord))
-                || (consideredWord.length() > 1 && !AutoCorrection.isInTheDictionary(mDictionaries,
+                || (consideredWord.length() > 1 && !AutoCorrectionUtils.isValidWord(this,
                         consideredWord, wordComposer.isFirstCharCapitalized()));
 
         final boolean hasAutoCorrection;
@@ -250,7 +282,7 @@
             // auto-correct.
             hasAutoCorrection = false;
         } else {
-            hasAutoCorrection = AutoCorrection.suggestionExceedsAutoCorrectionThreshold(
+            hasAutoCorrection = AutoCorrectionUtils.suggestionExceedsAutoCorrectionThreshold(
                     suggestionsSet.first(), consideredWord, mAutoCorrectionThreshold);
         }
 
@@ -379,7 +411,8 @@
                     typedWord, cur.toString(), cur.mScore);
             final String scoreInfoString;
             if (normalizedScore > 0) {
-                scoreInfoString = String.format("%d (%4.2f)", cur.mScore, normalizedScore);
+                scoreInfoString = String.format(
+                        Locale.ROOT, "%d (%4.2f)", cur.mScore, normalizedScore);
             } else {
                 scoreInfoString = Integer.toString(cur.mScore);
             }
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index dfddb0f..22beaef 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -19,11 +19,17 @@
 import android.text.TextUtils;
 import android.view.inputmethod.CompletionInfo;
 
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 
 public final class SuggestedWords {
+    public static final int INDEX_OF_TYPED_WORD = 0;
+    public static final int INDEX_OF_AUTO_CORRECTION = 1;
+
     private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST =
             CollectionUtils.newArrayList(0);
     public static final SuggestedWords EMPTY = new SuggestedWords(
@@ -61,12 +67,27 @@
         return mSuggestedWordInfoList.size();
     }
 
-    public String getWord(int pos) {
-        return mSuggestedWordInfoList.get(pos).mWord;
+    public String getWord(final int index) {
+        return mSuggestedWordInfoList.get(index).mWord;
     }
 
-    public SuggestedWordInfo getInfo(int pos) {
-        return mSuggestedWordInfoList.get(pos);
+    public SuggestedWordInfo getInfo(final int index) {
+        return mSuggestedWordInfoList.get(index);
+    }
+
+    public String getDebugString(final int pos) {
+        if (!LatinImeLogger.sDBG) {
+            return null;
+        }
+        final SuggestedWordInfo wordInfo = getInfo(pos);
+        if (wordInfo == null) {
+            return null;
+        }
+        final String debugString = wordInfo.getDebugString();
+        if (TextUtils.isEmpty(debugString)) {
+            return null;
+        }
+        return debugString;
     }
 
     public boolean willAutoCorrect() {
@@ -108,8 +129,8 @@
                 SuggestedWordInfo.KIND_TYPED, Dictionary.TYPE_USER_TYPED));
         alreadySeen.add(typedWord.toString());
         final int previousSize = previousSuggestions.size();
-        for (int pos = 1; pos < previousSize; pos++) {
-            final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(pos);
+        for (int index = 1; index < previousSize; index++) {
+            final SuggestedWordInfo prevWordInfo = previousSuggestions.getInfo(index);
             final String prevWord = prevWordInfo.mWord;
             // Filter out duplicate suggestion.
             if (!alreadySeen.contains(prevWord)) {
@@ -132,7 +153,10 @@
         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)
+        // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only
+        // in java for re-correction)
+        public static final int KIND_RESUMED = 9;
+        public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction
 
         public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
         public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index 90f9297..ed6fefa 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -28,6 +28,8 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 import java.util.Arrays;
 import java.util.Locale;
@@ -75,7 +77,7 @@
             final boolean alsoUseMoreRestrictiveLocales) {
         super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_USER);
         if (null == locale) throw new NullPointerException(); // Catch the error earlier
-        if (SubtypeLocale.NO_LANGUAGE.equals(locale)) {
+        if (SubtypeLocaleUtils.NO_LANGUAGE.equals(locale)) {
             // If we don't have a locale, insert into the "all locales" user dictionary.
             mLocale = USER_DICTIONARY_ALL_LANGUAGES;
         } else {
@@ -239,7 +241,6 @@
 
     private void addWords(final Cursor cursor) {
         final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-        clearFusionDictionary();
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
@@ -266,4 +267,9 @@
     protected boolean hasContentChanged() {
         return true;
     }
+
+    @Override
+    protected boolean needsToReloadBeforeWriting() {
+        return true;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
deleted file mode 100644
index 0f96c54..0000000
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ /dev/null
@@ -1,491 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.channels.FileChannel;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-public final class Utils {
-    private static final String TAG = Utils.class.getSimpleName();
-
-    private Utils() {
-        // This utility class is not publicly instantiable.
-    }
-
-    /**
-     * Cancel an {@link AsyncTask}.
-     *
-     * @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
-     *        task should be interrupted; otherwise, in-progress tasks are allowed
-     *        to complete.
-     */
-    public static void cancelTask(AsyncTask<?, ?, ?> task, boolean mayInterruptIfRunning) {
-        if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) {
-            task.cancel(mayInterruptIfRunning);
-        }
-    }
-
-    /* package */ static final class RingCharBuffer {
-        private static RingCharBuffer sRingCharBuffer = new RingCharBuffer();
-        private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
-        private static final int INVALID_COORDINATE = -2;
-        /* package */ static final int BUFSIZE = 20;
-        private InputMethodService mContext;
-        private boolean mEnabled = false;
-        private int mEnd = 0;
-        /* package */ int mLength = 0;
-        private char[] mCharBuf = new char[BUFSIZE];
-        private int[] mXBuf = new int[BUFSIZE];
-        private int[] mYBuf = new int[BUFSIZE];
-
-        private RingCharBuffer() {
-            // Intentional empty constructor for singleton.
-        }
-        @UsedForTesting
-        public static RingCharBuffer getInstance() {
-            return sRingCharBuffer;
-        }
-        public static RingCharBuffer init(InputMethodService context, boolean enabled,
-                boolean usabilityStudy) {
-            if (!(enabled || usabilityStudy)) return null;
-            sRingCharBuffer.mContext = context;
-            sRingCharBuffer.mEnabled = true;
-            UsabilityStudyLogUtils.getInstance().init(context);
-            return sRingCharBuffer;
-        }
-        private static int normalize(int in) {
-            int ret = in % BUFSIZE;
-            return ret < 0 ? ret + BUFSIZE : ret;
-        }
-        // TODO: accept code points
-        @UsedForTesting
-        public void push(char c, int x, int y) {
-            if (!mEnabled) return;
-            mCharBuf[mEnd] = c;
-            mXBuf[mEnd] = x;
-            mYBuf[mEnd] = y;
-            mEnd = normalize(mEnd + 1);
-            if (mLength < BUFSIZE) {
-                ++mLength;
-            }
-        }
-        public char pop() {
-            if (mLength < 1) {
-                return PLACEHOLDER_DELIMITER_CHAR;
-            } else {
-                mEnd = normalize(mEnd - 1);
-                --mLength;
-                return mCharBuf[mEnd];
-            }
-        }
-        public char getBackwardNthChar(int n) {
-            if (mLength <= n || n < 0) {
-                return PLACEHOLDER_DELIMITER_CHAR;
-            } else {
-                return mCharBuf[normalize(mEnd - n - 1)];
-            }
-        }
-        public int getPreviousX(char c, int back) {
-            int index = normalize(mEnd - 2 - back);
-            if (mLength <= back
-                    || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
-                return INVALID_COORDINATE;
-            } else {
-                return mXBuf[index];
-            }
-        }
-        public int getPreviousY(char c, int back) {
-            int index = normalize(mEnd - 2 - back);
-            if (mLength <= back
-                    || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
-                return INVALID_COORDINATE;
-            } else {
-                return mYBuf[index];
-            }
-        }
-        public String getLastWord(int ignoreCharCount) {
-            StringBuilder sb = new StringBuilder();
-            int i = ignoreCharCount;
-            for (; i < mLength; ++i) {
-                char c = mCharBuf[normalize(mEnd - 1 - i)];
-                if (!((LatinIME)mContext).isWordSeparator(c)) {
-                    break;
-                }
-            }
-            for (; i < mLength; ++i) {
-                char c = mCharBuf[normalize(mEnd - 1 - i)];
-                if (!((LatinIME)mContext).isWordSeparator(c)) {
-                    sb.append(c);
-                } else {
-                    break;
-                }
-            }
-            return sb.reverse().toString();
-        }
-        public void reset() {
-            mLength = 0;
-        }
-    }
-
-    // Get the current stack trace
-    public static String getStackTrace(final int limit) {
-        StringBuilder sb = new StringBuilder();
-        try {
-            throw new RuntimeException();
-        } catch (RuntimeException e) {
-            StackTraceElement[] frames = e.getStackTrace();
-            // Start at 1 because the first frame is here and we don't care about it
-            for (int j = 1; j < frames.length && j < limit + 1; ++j) {
-                sb.append(frames[j].toString() + "\n");
-            }
-        }
-        return sb.toString();
-    }
-
-    public static String getStackTrace() {
-        return getStackTrace(Integer.MAX_VALUE - 1);
-    }
-
-    public static final class UsabilityStudyLogUtils {
-        // TODO: remove code duplication with ResearchLog class
-        private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
-        private static final String FILENAME = "log.txt";
-        private final Handler mLoggingHandler;
-        private File mFile;
-        private File mDirectory;
-        private InputMethodService mIms;
-        private PrintWriter mWriter;
-        private final Date mDate;
-        private final SimpleDateFormat mDateFormat;
-
-        private UsabilityStudyLogUtils() {
-            mDate = new Date();
-            mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
-            HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
-                    Process.THREAD_PRIORITY_BACKGROUND);
-            handlerThread.start();
-            mLoggingHandler = new Handler(handlerThread.getLooper());
-        }
-
-        // Initialization-on-demand holder
-        private static final class OnDemandInitializationHolder {
-            public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
-        }
-
-        public static UsabilityStudyLogUtils getInstance() {
-            return OnDemandInitializationHolder.sInstance;
-        }
-
-        public void init(InputMethodService ims) {
-            mIms = ims;
-            mDirectory = ims.getFilesDir();
-        }
-
-        private void createLogFileIfNotExist() {
-            if ((mFile == null || !mFile.exists())
-                    && (mDirectory != null && mDirectory.exists())) {
-                try {
-                    mWriter = getPrintWriter(mDirectory, FILENAME, false);
-                } catch (IOException e) {
-                    Log.e(USABILITY_TAG, "Can't create log file.");
-                }
-            }
-        }
-
-        public static void writeBackSpace(int x, int y) {
-            UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
-        }
-
-        public void writeChar(char c, int x, int y) {
-            String inputChar = String.valueOf(c);
-            switch (c) {
-                case '\n':
-                    inputChar = "<enter>";
-                    break;
-                case '\t':
-                    inputChar = "<tab>";
-                    break;
-                case ' ':
-                    inputChar = "<space>";
-                    break;
-            }
-            UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
-            LatinImeLogger.onPrintAllUsabilityStudyLogs();
-        }
-
-        public void write(final String log) {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    createLogFileIfNotExist();
-                    final long currentTime = System.currentTimeMillis();
-                    mDate.setTime(currentTime);
-
-                    final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
-                            mDateFormat.format(mDate), currentTime, log);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Write: " + log);
-                    }
-                    mWriter.print(printString);
-                }
-            });
-        }
-
-        private synchronized String getBufferedLogs() {
-            mWriter.flush();
-            StringBuilder sb = new StringBuilder();
-            BufferedReader br = getBufferedReader();
-            String line;
-            try {
-                while ((line = br.readLine()) != null) {
-                    sb.append('\n');
-                    sb.append(line);
-                }
-            } catch (IOException e) {
-                Log.e(USABILITY_TAG, "Can't read log file.");
-            } finally {
-                if (LatinImeLogger.sDBG) {
-                    Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
-                }
-                try {
-                    br.close();
-                } catch (IOException e) {
-                    // ignore.
-                }
-            }
-            return sb.toString();
-        }
-
-        public void emailResearcherLogsAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final Date date = new Date();
-                    date.setTime(System.currentTimeMillis());
-                    final String currentDateTimeString =
-                            new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
-                    if (mFile == null) {
-                        Log.w(USABILITY_TAG, "No internal log file found.");
-                        return;
-                    }
-                    if (mIms.checkCallingOrSelfPermission(
-                                android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                                        != PackageManager.PERMISSION_GRANTED) {
-                        Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
-                        return;
-                    }
-                    mWriter.flush();
-                    final String destPath = Environment.getExternalStorageDirectory()
-                            + "/research-" + currentDateTimeString + ".log";
-                    final File destFile = new File(destPath);
-                    try {
-                        final FileInputStream srcStream = new FileInputStream(mFile);
-                        final FileOutputStream destStream = new FileOutputStream(destFile);
-                        final FileChannel src = srcStream.getChannel();
-                        final FileChannel dest = destStream.getChannel();
-                        src.transferTo(0, src.size(), dest);
-                        src.close();
-                        srcStream.close();
-                        dest.close();
-                        destStream.close();
-                    } catch (FileNotFoundException e1) {
-                        Log.w(USABILITY_TAG, e1);
-                        return;
-                    } catch (IOException e2) {
-                        Log.w(USABILITY_TAG, e2);
-                        return;
-                    }
-                    if (destFile == null || !destFile.exists()) {
-                        Log.w(USABILITY_TAG, "Dest file doesn't exist.");
-                        return;
-                    }
-                    final Intent intent = new Intent(Intent.ACTION_SEND);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
-                    }
-                    intent.setType("text/plain");
-                    intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
-                    intent.putExtra(Intent.EXTRA_SUBJECT,
-                            "[Research Logs] " + currentDateTimeString);
-                    mIms.startActivity(intent);
-                }
-            });
-        }
-
-        public void printAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
-                }
-            });
-        }
-
-        public void clearAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mFile != null && mFile.exists()) {
-                        if (LatinImeLogger.sDBG) {
-                            Log.d(USABILITY_TAG, "Delete log file.");
-                        }
-                        mFile.delete();
-                        mWriter.close();
-                    }
-                }
-            });
-        }
-
-        private BufferedReader getBufferedReader() {
-            createLogFileIfNotExist();
-            try {
-                return new BufferedReader(new FileReader(mFile));
-            } catch (FileNotFoundException e) {
-                return null;
-            }
-        }
-
-        private PrintWriter getPrintWriter(
-                File dir, String filename, boolean renew) throws IOException {
-            mFile = new File(dir, filename);
-            if (mFile.exists()) {
-                if (renew) {
-                    mFile.delete();
-                }
-            }
-            return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
-        }
-    }
-
-    public static final class Stats {
-        public static void onNonSeparator(final char code, final int x,
-                final int y) {
-            RingCharBuffer.getInstance().push(code, x, y);
-            LatinImeLogger.logOnInputChar();
-        }
-
-        public static void onSeparator(final int code, final int x, final int y) {
-            // Helper method to log a single code point separator
-            // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
-            onSeparator(new String(new int[]{code}, 0, 1), x, y);
-        }
-
-        public static void onSeparator(final String separator, final int x, final int y) {
-            final int length = separator.length();
-            for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
-                int codePoint = Character.codePointAt(separator, i);
-                // TODO: accept code points
-                RingCharBuffer.getInstance().push((char)codePoint, x, y);
-            }
-            LatinImeLogger.logOnInputSeparator();
-        }
-
-        public static void onAutoCorrection(final String typedWord, final String correctedWord,
-                final String separatorString, final WordComposer wordComposer) {
-            final boolean isBatchMode = wordComposer.isBatchMode();
-            if (!isBatchMode && TextUtils.isEmpty(typedWord)) return;
-            // TODO: this fails when the separator is more than 1 code point long, but
-            // the backend can't handle it yet. The only case when this happens is with
-            // smileys and other multi-character keys.
-            final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
-                    : separatorString.codePointAt(0);
-            if (!isBatchMode) {
-                LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
-            } else {
-                if (!TextUtils.isEmpty(correctedWord)) {
-                    // We must make sure that InputPointer contains only the relative timestamps,
-                    // not actual timestamps.
-                    LatinImeLogger.logOnAutoCorrectionForGeometric(
-                            "", correctedWord, codePoint, wordComposer.getInputPointers());
-                }
-            }
-        }
-
-        public static void onAutoCorrectionCancellation() {
-            LatinImeLogger.logOnAutoCorrectionCancelled();
-        }
-    }
-
-    public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
-        if (!LatinImeLogger.sDBG) return null;
-        final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-        if (wordInfo == null) return null;
-        final String info = wordInfo.getDebugString();
-        if (TextUtils.isEmpty(info)) return null;
-        return info;
-    }
-
-    public static int getAcitivityTitleResId(Context context, Class<? extends Activity> cls) {
-        final ComponentName cn = new ComponentName(context, cls);
-        try {
-            final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
-            if (ai != null) {
-                return ai.labelRes;
-            }
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Failed to get settings activity title res id.", e);
-        }
-        return 0;
-    }
-
-    public static String getVersionName(Context context) {
-        try {
-            if (context == null) {
-                return "";
-            }
-            final String packageName = context.getPackageName();
-            PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
-            return info.versionName;
-        } catch (NameNotFoundException e) {
-            Log.e(TAG, "Could not find version info.", e);
-        }
-        return "";
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e078f03..a09ca60 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -18,6 +18,7 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.Arrays;
 
@@ -25,7 +26,7 @@
  * A place to store the currently composing word with information such as adjacent key codes as well
  */
 public final class WordComposer {
-    private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+    private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
     private static final boolean DBG = LatinImeLogger.sDBG;
 
     public static final int CAPS_MODE_OFF = 0;
@@ -36,8 +37,16 @@
     public static final int CAPS_MODE_AUTO_SHIFTED = 0x5;
     public static final int CAPS_MODE_AUTO_SHIFT_LOCKED = 0x7;
 
+    // An array of code points representing the characters typed so far.
+    // The array is limited to MAX_WORD_LENGTH code points, but mTypedWord extends past that
+    // and mCodePointSize can go past that. If mCodePointSize is greater than MAX_WORD_LENGTH,
+    // this just does not contain the associated code points past MAX_WORD_LENGTH.
     private int[] mPrimaryKeyCodes;
     private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH);
+    // This is the typed word, as a StringBuilder. This has the same contents as mPrimaryKeyCodes
+    // but under a StringBuilder representation for ease of use, depending on what is more useful
+    // at any given time. However this is not limited in size, while mPrimaryKeyCodes is limited
+    // to MAX_WORD_LENGTH code points.
     private final StringBuilder mTypedWord;
     private String mAutoCorrection;
     private boolean mIsResumed;
@@ -55,6 +64,10 @@
     private int mDigitsCount;
     private int mCapitalizedMode;
     private int mTrailingSingleQuotesCount;
+    // This is the number of code points entered so far. This is not limited to MAX_WORD_LENGTH.
+    // In general, this contains the size of mPrimaryKeyCodes, except when this is greater than
+    // MAX_WORD_LENGTH in which case mPrimaryKeyCodes only contain the first MAX_WORD_LENGTH
+    // code points.
     private int mCodePointSize;
     private int mCursorPositionWithinWord;
 
@@ -192,6 +205,49 @@
         return mCursorPositionWithinWord != mCodePointSize;
     }
 
+    /**
+     * When the cursor is moved by the user, we need to update its position.
+     * If it falls inside the currently composing word, we don't reset the composition, and
+     * only update the cursor position.
+     *
+     * @param expectedMoveAmount How many java chars to move the cursor. Negative values move
+     * the cursor backward, positive values move the cursor forward.
+     * @return true if the cursor is still inside the composing word, false otherwise.
+     */
+    public boolean moveCursorByAndReturnIfInsideComposingWord(final int expectedMoveAmount) {
+        int actualMoveAmountWithinWord = 0;
+        int cursorPos = mCursorPositionWithinWord;
+        final int[] codePoints;
+        if (mCodePointSize >= MAX_WORD_LENGTH) {
+            // If we have more than MAX_WORD_LENGTH characters, we don't have everything inside
+            // mPrimaryKeyCodes. This should be rare enough that we can afford to just compute
+            // the array on the fly when this happens.
+            codePoints = StringUtils.toCodePointArray(mTypedWord.toString());
+        } else {
+            codePoints = mPrimaryKeyCodes;
+        }
+        if (expectedMoveAmount >= 0) {
+            // Moving the cursor forward for the expected amount or until the end of the word has
+            // been reached, whichever comes first.
+            while (actualMoveAmountWithinWord < expectedMoveAmount && cursorPos < mCodePointSize) {
+                actualMoveAmountWithinWord += Character.charCount(codePoints[cursorPos]);
+                ++cursorPos;
+            }
+        } else {
+            // Moving the cursor backward for the expected amount or until the start of the word
+            // has been reached, whichever comes first.
+            while (actualMoveAmountWithinWord > expectedMoveAmount && cursorPos > 0) {
+                --cursorPos;
+                actualMoveAmountWithinWord -= Character.charCount(codePoints[cursorPos]);
+            }
+        }
+        // If the actual and expected amounts differ, we crossed the start or the end of the word
+        // so the result would not be inside the composing word.
+        if (actualMoveAmountWithinWord != expectedMoveAmount) return false;
+        mCursorPositionWithinWord = cursorPos;
+        return true;
+    }
+
     public void setBatchInputPointers(final InputPointers batchPointers) {
         mInputPointers.set(batchPointers);
         mIsBatchMode = true;
diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
similarity index 80%
rename from java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
rename to java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
index 9f91639..028f78a 100644
--- a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java
+++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java
@@ -14,15 +14,22 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.debug;
 
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
 import android.content.DialogInterface.OnClickListener;
 import android.os.Environment;
 
+import com.android.inputmethod.latin.BinaryDictionaryFileDumper;
+import com.android.inputmethod.latin.BinaryDictionaryGetter;
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.DictionaryInfoUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -56,7 +63,7 @@
         if (0 == fileNames.length) {
             showNoFileDialog(context);
         } else if (1 == fileNames.length) {
-            askInstallFile(context, fileNames[0]);
+            askInstallFile(context, SOURCE_FOLDER, fileNames[0], null /* completeRunnable */);
         } else {
             showChooseFileDialog(context, fileNames);
         }
@@ -79,14 +86,19 @@
                 .setItems(fileNames, new OnClickListener() {
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
-                        askInstallFile(context, fileNames[which]);
+                        askInstallFile(context, SOURCE_FOLDER, fileNames[which],
+                                null /* completeRunnable */);
                     }
                 })
                 .create().show();
     }
 
-    private static void askInstallFile(final Context context, final String fileName) {
-        final File file = new File(SOURCE_FOLDER, fileName.toString());
+    /**
+     * Shows a dialog which offers the user to install the external dictionary.
+     */
+    public static void askInstallFile(final Context context, final String dirPath,
+            final String fileName, final Runnable completeRunnable) {
+        final File file = new File(dirPath, fileName.toString());
         final FileHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
         final StringBuilder message = new StringBuilder();
         final String locale = header.getLocaleString();
@@ -106,12 +118,26 @@
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
                         dialog.dismiss();
+                        if (completeRunnable != null) {
+                            completeRunnable.run();
+                        }
                     }
                 }).setPositiveButton(android.R.string.ok, new OnClickListener() {
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
                         installFile(context, file, header);
                         dialog.dismiss();
+                        if (completeRunnable != null) {
+                            completeRunnable.run();
+                        }
+                    }
+                }).setOnCancelListener(new OnCancelListener() {
+                    @Override
+                    public void onCancel(DialogInterface dialog) {
+                        // Canceled by the user by hitting the back key
+                        if (completeRunnable != null) {
+                            completeRunnable.run();
+                        }
                     }
                 }).create().show();
     }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index c87a925..167c691 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -181,7 +181,7 @@
         final FileHeader header = BinaryDictInputOutput.readHeader(buffer);
         int wordPos = 0;
         final int wordLen = word.codePointCount(0, word.length());
-        for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+        for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
             if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD;
 
             do {
@@ -746,7 +746,7 @@
         final int[] codePoints = FusionDictionary.getCodePoints(word);
         final int wordLen = codePoints.length;
 
-        for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) {
+        for (int depth = 0; depth < Constants.DICTIONARY_MAX_WORD_LENGTH; ++depth) {
             if (wordPos >= wordLen) break;
             nodeOriginAddress = buffer.position();
             int nodeParentAddress = -1;
@@ -982,6 +982,7 @@
         return null;
     }
 
+    private static final int HEADER_READING_BUFFER_SIZE = 16384;
     /**
      * Convenience method to read the header of a binary file.
      *
@@ -991,7 +992,6 @@
      * @param offset The offset in the file where to start reading the data.
      * @param length The length of the data file.
      */
-    private static final int HEADER_READING_BUFFER_SIZE = 16384;
     public static FileHeader getDictionaryFileHeader(
             final File file, final long offset, final long length)
             throws FileNotFoundException, IOException, UnsupportedFormatException {
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 467f6a0..1b187d8 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -389,7 +389,7 @@
      * @param node the node to compute the maximum size of.
      * @param options file format options.
      */
-    private static void setNodeMaximumSize(final Node node, final FormatOptions options) {
+    private static void calculateNodeMaximumSize(final Node node, final FormatOptions options) {
         int size = getGroupCountSize(node);
         for (CharGroup g : node.mData) {
             final int groupSize = getCharGroupMaximumSize(g, options);
@@ -518,14 +518,56 @@
     }
 
     /**
-     * Finds the absolute address of a word in the dictionary.
+     * Get the offset from a position inside a current node to a target node, during update.
      *
-     * @param dict the dictionary in which to search.
-     * @param word the word we are searching for.
-     * @return the word address. If it is not found, an exception is thrown.
+     * If the current node is before the target node, the target node has not been updated yet,
+     * so we should return the offset from the old position of the current node to the old position
+     * of the target node. If on the other hand the target is before the current node, it already
+     * has been updated, so we should return the offset from the new position in the current node
+     * to the new position in the target node.
+     * @param currentNode the node containing the CharGroup where the offset will be written
+     * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
+     * @param targetNode the target node to get the offset to
+     * @return the offset to the target node
      */
-    private static int findAddressOfWord(final FusionDictionary dict, final String word) {
-        return FusionDictionary.findWordInTree(dict.mRoot, word).mCachedAddress;
+    private static int getOffsetToTargetNodeDuringUpdate(final Node currentNode,
+            final int offsetFromStartOfCurrentNode, final Node targetNode) {
+        final boolean isTargetBeforeCurrent = (targetNode.mCachedAddressBeforeUpdate
+                < currentNode.mCachedAddressBeforeUpdate);
+        if (isTargetBeforeCurrent) {
+            return targetNode.mCachedAddressAfterUpdate
+                    - (currentNode.mCachedAddressAfterUpdate + offsetFromStartOfCurrentNode);
+        } else {
+            return targetNode.mCachedAddressBeforeUpdate
+                    - (currentNode.mCachedAddressBeforeUpdate + offsetFromStartOfCurrentNode);
+        }
+    }
+
+    /**
+     * Get the offset from a position inside a current node to a target CharGroup, during update.
+     * @param currentNode the node containing the CharGroup where the offset will be written
+     * @param offsetFromStartOfCurrentNode the offset, in bytes, from the start of currentNode
+     * @param targetCharGroup the target CharGroup to get the offset to
+     * @return the offset to the target CharGroup
+     */
+    // TODO: is there any way to factorize this method with the one above?
+    private static int getOffsetToTargetCharGroupDuringUpdate(final Node currentNode,
+            final int offsetFromStartOfCurrentNode, final CharGroup targetCharGroup) {
+        final int oldOffsetBasePoint = currentNode.mCachedAddressBeforeUpdate
+                + offsetFromStartOfCurrentNode;
+        final boolean isTargetBeforeCurrent = (targetCharGroup.mCachedAddressBeforeUpdate
+                < oldOffsetBasePoint);
+        // If the target is before the current node, then its address has already been updated.
+        // We can use the AfterUpdate member, and compare it to our own member after update.
+        // Otherwise, the AfterUpdate member is not updated yet, so we need to use the BeforeUpdate
+        // member, and of course we have to compare this to our own address before update.
+        if (isTargetBeforeCurrent) {
+            final int newOffsetBasePoint = currentNode.mCachedAddressAfterUpdate
+                    + offsetFromStartOfCurrentNode;
+            return targetCharGroup.mCachedAddressAfterUpdate - newOffsetBasePoint;
+        } else {
+            return targetCharGroup.mCachedAddressBeforeUpdate - oldOffsetBasePoint;
+        }
     }
 
     /**
@@ -548,33 +590,28 @@
         boolean changed = false;
         int size = getGroupCountSize(node);
         for (CharGroup group : node.mData) {
-            if (group.mCachedAddress != node.mCachedAddress + size) {
+            group.mCachedAddressAfterUpdate = node.mCachedAddressAfterUpdate + size;
+            if (group.mCachedAddressAfterUpdate != group.mCachedAddressBeforeUpdate) {
                 changed = true;
-                group.mCachedAddress = node.mCachedAddress + size;
             }
             int groupSize = getGroupHeaderSize(group, formatOptions);
             if (group.isTerminal()) groupSize += FormatSpec.GROUP_FREQUENCY_SIZE;
             if (null == group.mChildren && formatOptions.mSupportsDynamicUpdate) {
                 groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
             } else if (null != group.mChildren) {
-                final int offsetBasePoint = groupSize + node.mCachedAddress + size;
-                final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
-                // assign my address to children's parent address
-                group.mChildren.mCachedParentAddress = group.mCachedAddress
-                        - group.mChildren.mCachedAddress;
                 if (formatOptions.mSupportsDynamicUpdate) {
                     groupSize += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE;
                 } else {
-                    groupSize += getByteSize(offset);
+                    groupSize += getByteSize(getOffsetToTargetNodeDuringUpdate(node,
+                            groupSize + size, group.mChildren));
                 }
             }
             groupSize += getShortcutListSize(group.mShortcutTargets);
             if (null != group.mBigrams) {
                 for (WeightedString bigram : group.mBigrams) {
-                    final int offsetBasePoint = groupSize + node.mCachedAddress + size
-                            + FormatSpec.GROUP_FLAGS_SIZE;
-                    final int addressOfBigram = findAddressOfWord(dict, bigram.mWord);
-                    final int offset = addressOfBigram - offsetBasePoint;
+                    final int offset = getOffsetToTargetCharGroupDuringUpdate(node,
+                            groupSize + size + FormatSpec.GROUP_FLAGS_SIZE,
+                            FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord));
                     groupSize += getByteSize(offset) + FormatSpec.GROUP_FLAGS_SIZE;
                 }
             }
@@ -592,35 +629,69 @@
     }
 
     /**
-     * Computes the byte size of a list of nodes and updates each node cached position.
+     * Initializes the cached addresses of nodes from their size.
      *
      * @param flatNodes the array of nodes.
      * @param formatOptions file format options.
      * @return the byte size of the entire stack.
      */
-    private static int stackNodes(final ArrayList<Node> flatNodes,
+    private static int initializeNodesCachedAddresses(final ArrayList<Node> flatNodes,
             final FormatOptions formatOptions) {
         int nodeOffset = 0;
-        for (Node n : flatNodes) {
-            n.mCachedAddress = nodeOffset;
+        for (final Node n : flatNodes) {
+            n.mCachedAddressBeforeUpdate = nodeOffset;
             int groupCountSize = getGroupCountSize(n);
             int groupOffset = 0;
-            for (CharGroup g : n.mData) {
-                g.mCachedAddress = groupCountSize + nodeOffset + groupOffset;
+            for (final CharGroup g : n.mData) {
+                g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate =
+                        groupCountSize + nodeOffset + groupOffset;
                 groupOffset += g.mCachedSize;
             }
             final int nodeSize = groupCountSize + groupOffset
                     + (formatOptions.mSupportsDynamicUpdate
                             ? FormatSpec.FORWARD_LINK_ADDRESS_SIZE : 0);
-            if (nodeSize != n.mCachedSize) {
-                throw new RuntimeException("Bug : Stored and computed node size differ");
-            }
             nodeOffset += n.mCachedSize;
         }
         return nodeOffset;
     }
 
     /**
+     * Updates the cached addresses of nodes after recomputing their new positions.
+     *
+     * @param flatNodes the array of nodes.
+     */
+    private static void updateNodeCachedAddresses(final ArrayList<Node> flatNodes) {
+        for (final Node n : flatNodes) {
+            n.mCachedAddressBeforeUpdate = n.mCachedAddressAfterUpdate;
+            for (final CharGroup g : n.mData) {
+                g.mCachedAddressBeforeUpdate = g.mCachedAddressAfterUpdate;
+            }
+        }
+    }
+
+    /**
+     * Compute the cached parent addresses after all has been updated.
+     *
+     * The parent addresses are used by some binary formats at write-to-disk time. Not all formats
+     * need them. In particular, version 2 does not need them, and version 3 does.
+     *
+     * @param flatNodes the flat array of nodes to fill in
+     */
+    private static void computeParentAddresses(final ArrayList<Node> flatNodes) {
+        for (final Node node : flatNodes) {
+            for (final CharGroup group : node.mData) {
+                if (null != group.mChildren) {
+                    // Assign my address to children's parent address
+                    // Here BeforeUpdate and AfterUpdate addresses have the same value, so it
+                    // does not matter which we use.
+                    group.mChildren.mCachedParentAddress = group.mCachedAddressAfterUpdate
+                            - group.mChildren.mCachedAddressAfterUpdate;
+                }
+            }
+        }
+    }
+
+    /**
      * Compute the addresses and sizes of an ordered node array.
      *
      * This method takes a node array and will update its cached address and size values
@@ -637,9 +708,9 @@
      */
     private static ArrayList<Node> computeAddresses(final FusionDictionary dict,
             final ArrayList<Node> flatNodes, final FormatOptions formatOptions) {
-        // First get the worst sizes and offsets
-        for (Node n : flatNodes) setNodeMaximumSize(n, formatOptions);
-        final int offset = stackNodes(flatNodes, formatOptions);
+        // First get the worst possible sizes and offsets
+        for (final Node n : flatNodes) calculateNodeMaximumSize(n, formatOptions);
+        final int offset = initializeNodesCachedAddresses(flatNodes, formatOptions);
 
         MakedictLog.i("Compressing the array addresses. Original size : " + offset);
         MakedictLog.i("(Recursively seen size : " + offset + ")");
@@ -648,22 +719,28 @@
         boolean changesDone = false;
         do {
             changesDone = false;
-            for (Node n : flatNodes) {
+            int nodeStartOffset = 0;
+            for (final Node n : flatNodes) {
+                n.mCachedAddressAfterUpdate = nodeStartOffset;
                 final int oldNodeSize = n.mCachedSize;
                 final boolean changed = computeActualNodeSize(n, dict, formatOptions);
                 final int newNodeSize = n.mCachedSize;
                 if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
+                nodeStartOffset += newNodeSize;
                 changesDone |= changed;
             }
-            stackNodes(flatNodes, formatOptions);
+            updateNodeCachedAddresses(flatNodes);
             ++passes;
             if (passes > MAX_PASSES) throw new RuntimeException("Too many passes - probably a bug");
         } while (changesDone);
 
+        if (formatOptions.mSupportsDynamicUpdate) {
+            computeParentAddresses(flatNodes);
+        }
         final Node lastNode = flatNodes.get(flatNodes.size() - 1);
         MakedictLog.i("Compression complete in " + passes + " passes.");
         MakedictLog.i("After address compression : "
-                + (lastNode.mCachedAddress + lastNode.mCachedSize));
+                + (lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize));
 
         return flatNodes;
     }
@@ -681,10 +758,12 @@
     private static void checkFlatNodeArray(final ArrayList<Node> array) {
         int offset = 0;
         int index = 0;
-        for (Node n : array) {
-            if (n.mCachedAddress != offset) {
+        for (final Node n : array) {
+            // BeforeUpdate and AfterUpdate addresses are the same here, so it does not matter
+            // which we use.
+            if (n.mCachedAddressAfterUpdate != offset) {
                 throw new RuntimeException("Wrong address for node " + index
-                        + " : expected " + offset + ", got " + n.mCachedAddress);
+                        + " : expected " + offset + ", got " + n.mCachedAddressAfterUpdate);
             }
             ++index;
             offset += n.mCachedSize;
@@ -926,7 +1005,7 @@
     private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
             final Node node, final FormatOptions formatOptions) {
         // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
-        int index = node.mCachedAddress;
+        int index = node.mCachedAddressAfterUpdate;
 
         final int groupCount = node.mData.size();
         final int countSize = getGroupCountSize(node);
@@ -943,10 +1022,11 @@
         }
         int groupAddress = index;
         for (int i = 0; i < groupCount; ++i) {
-            CharGroup group = node.mData.get(i);
-            if (index != group.mCachedAddress) throw new RuntimeException("Bug: write index is not "
-                    + "the same as the cached address of the group : "
-                    + index + " <> " + group.mCachedAddress);
+            final CharGroup group = node.mData.get(i);
+            if (index != group.mCachedAddressAfterUpdate) {
+                throw new RuntimeException("Bug: write index is not the same as the cached address "
+                        + "of the group : " + index + " <> " + group.mCachedAddressAfterUpdate);
+            }
             groupAddress += getGroupHeaderSize(group, formatOptions);
             // Sanity checks.
             if (DBG && group.mFrequency > FormatSpec.MAX_TERMINAL_FREQUENCY) {
@@ -957,15 +1037,15 @@
             if (group.mFrequency >= 0) groupAddress += FormatSpec.GROUP_FREQUENCY_SIZE;
             final int childrenOffset = null == group.mChildren
                     ? FormatSpec.NO_CHILDREN_ADDRESS
-                            : group.mChildren.mCachedAddress - groupAddress;
-            byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
-            buffer[index++] = flags;
+                            : group.mChildren.mCachedAddressAfterUpdate - groupAddress;
+            buffer[index++] =
+                    makeCharGroupFlags(group, groupAddress, childrenOffset, formatOptions);
 
             if (parentAddress == FormatSpec.NO_PARENT_ADDRESS) {
                 index = writeParentAddress(buffer, index, parentAddress, formatOptions);
             } else {
-                index = writeParentAddress(buffer, index,
-                        parentAddress + (node.mCachedAddress - group.mCachedAddress),
+                index = writeParentAddress(buffer, index, parentAddress
+                        + (node.mCachedAddressAfterUpdate - group.mCachedAddressAfterUpdate),
                         formatOptions);
             }
 
@@ -1016,7 +1096,7 @@
                     final WeightedString bigram = bigramIterator.next();
                     final CharGroup target =
                             FusionDictionary.findWordInTree(dict.mRoot, bigram.mWord);
-                    final int addressOfBigram = target.mCachedAddress;
+                    final int addressOfBigram = target.mCachedAddressAfterUpdate;
                     final int unigramFrequencyForThisWord = target.mFrequency;
                     ++groupAddress;
                     final int offset = addressOfBigram - groupAddress;
@@ -1035,9 +1115,9 @@
                     = FormatSpec.NO_FORWARD_LINK_ADDRESS;
             index += FormatSpec.FORWARD_LINK_ADDRESS_SIZE;
         }
-        if (index != node.mCachedAddress + node.mCachedSize) throw new RuntimeException(
+        if (index != node.mCachedAddressAfterUpdate + node.mCachedSize) throw new RuntimeException(
                 "Not the same size : written "
-                + (index - node.mCachedAddress) + " bytes out of a node that should have "
+                + (index - node.mCachedAddressAfterUpdate) + " bytes from a node that should have "
                 + node.mCachedSize + " bytes");
         return index;
     }
@@ -1057,25 +1137,27 @@
         int charGroups = 0;
         int maxGroups = 0;
         int maxRuns = 0;
-        for (Node n : nodes) {
+        for (final Node n : nodes) {
             if (maxGroups < n.mData.size()) maxGroups = n.mData.size();
-            for (CharGroup cg : n.mData) {
+            for (final CharGroup cg : n.mData) {
                 ++charGroups;
                 if (cg.mChars.length > maxRuns) maxRuns = cg.mChars.length;
                 if (cg.mFrequency >= 0) {
-                    if (n.mCachedAddress < firstTerminalAddress)
-                        firstTerminalAddress = n.mCachedAddress;
-                    if (n.mCachedAddress > lastTerminalAddress)
-                        lastTerminalAddress = n.mCachedAddress;
+                    if (n.mCachedAddressAfterUpdate < firstTerminalAddress)
+                        firstTerminalAddress = n.mCachedAddressAfterUpdate;
+                    if (n.mCachedAddressAfterUpdate > lastTerminalAddress)
+                        lastTerminalAddress = n.mCachedAddressAfterUpdate;
                 }
             }
-            if (n.mCachedAddress + n.mCachedSize > size) size = n.mCachedAddress + n.mCachedSize;
+            if (n.mCachedAddressAfterUpdate + n.mCachedSize > size) {
+                size = n.mCachedAddressAfterUpdate + n.mCachedSize;
+            }
         }
         final int[] groupCounts = new int[maxGroups + 1];
         final int[] runCounts = new int[maxRuns + 1];
-        for (Node n : nodes) {
+        for (final Node n : nodes) {
             ++groupCounts[n.mData.size()];
-            for (CharGroup cg : n.mData) {
+            for (final CharGroup cg : n.mData) {
                 ++runCounts[cg.mChars.length];
             }
         }
@@ -1185,7 +1267,7 @@
 
         // Create a buffer that matches the final dictionary size.
         final Node lastNode = flatNodes.get(flatNodes.size() - 1);
-        final int bufferSize = lastNode.mCachedAddress + lastNode.mCachedSize;
+        final int bufferSize = lastNode.mCachedAddressAfterUpdate + lastNode.mCachedSize;
         final byte[] buffer = new byte[bufferSize];
         int index = 0;
 
@@ -1564,8 +1646,9 @@
                 buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS);
 
         final Node node = new Node(nodeContents);
-        node.mCachedAddress = nodeOrigin;
-        reverseNodeMap.put(node.mCachedAddress, node);
+        node.mCachedAddressBeforeUpdate = nodeOrigin;
+        node.mCachedAddressAfterUpdate = nodeOrigin;
+        reverseNodeMap.put(node.mCachedAddressAfterUpdate, node);
         return node;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index e1e5e55..feadcda 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -167,7 +167,7 @@
 
     // TODO: Make this value adaptative to content data, store it in the header, and
     // use it in the reading code.
-    static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
+    static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
 
     static final int PARENT_ADDRESS_SIZE = 3;
     static final int FORWARD_LINK_ADDRESS_SIZE = 3;
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 17d2815..118dc22 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -34,6 +34,8 @@
 public final class FusionDictionary implements Iterable<Word> {
     private static final boolean DBG = MakedictLog.DBG;
 
+    private static int CHARACTER_NOT_FOUND_INDEX = -1;
+
     /**
      * A node of the dictionary, containing several CharGroups.
      *
@@ -46,7 +48,13 @@
         ArrayList<CharGroup> mData;
         // To help with binary generation
         int mCachedSize = Integer.MIN_VALUE;
-        int mCachedAddress = Integer.MIN_VALUE;
+        // mCachedAddressBefore/AfterUpdate are helpers for binary dictionary generation. They
+        // always hold the same value except between dictionary address compression, during which
+        // the update process needs to know about both values at the same time. Updating will
+        // update the AfterUpdate value, and the code will move them to BeforeUpdate before
+        // the next update pass.
+        int mCachedAddressBeforeUpdate = Integer.MIN_VALUE;
+        int mCachedAddressAfterUpdate = Integer.MIN_VALUE;
         int mCachedParentAddress = 0;
 
         public Node() {
@@ -105,9 +113,15 @@
         Node mChildren;
         boolean mIsNotAWord; // Only a shortcut
         boolean mIsBlacklistEntry;
-        // The two following members to help with binary generation
-        int mCachedSize;
-        int mCachedAddress;
+        // mCachedSize and mCachedAddressBefore/AfterUpdate are helpers for binary dictionary
+        // generation. Before and After always hold the same value except during dictionary
+        // address compression, where the update process needs to know about both values at the
+        // same time. Updating will update the AfterUpdate value, and the code will move them
+        // to BeforeUpdate before the next update pass.
+        // The update process does not need two versions of mCachedSize.
+        int mCachedSize; // The size, in bytes, of this char group.
+        int mCachedAddressBeforeUpdate; // The address of this char group (before update)
+        int mCachedAddressAfterUpdate; // The address of this char group (after update)
 
         public CharGroup(final int[] chars, final ArrayList<WeightedString> shortcutTargets,
                 final ArrayList<WeightedString> bigrams, final int frequency,
@@ -450,7 +464,7 @@
             final ArrayList<WeightedString> shortcutTargets,
             final boolean isNotAWord, final boolean isBlacklistEntry) {
         assert(frequency >= 0 && frequency <= 255);
-        if (word.length >= Constants.Dictionary.MAX_WORD_LENGTH) {
+        if (word.length >= Constants.DICTIONARY_MAX_WORD_LENGTH) {
             MakedictLog.w("Ignoring a word that is too long: word.length = " + word.length);
             return;
         }
@@ -461,7 +475,7 @@
         CharGroup currentGroup = null;
         int differentCharIndex = 0; // Set by the loop to the index of the char that differs
         int nodeIndex = findIndexOfChar(mRoot, word[charIndex]);
-        while (CHARACTER_NOT_FOUND != nodeIndex) {
+        while (CHARACTER_NOT_FOUND_INDEX != nodeIndex) {
             currentGroup = currentNode.mData.get(nodeIndex);
             differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex);
             if (ARRAYS_ARE_EQUAL != differentCharIndex
@@ -473,7 +487,7 @@
             nodeIndex = findIndexOfChar(currentNode, word[charIndex]);
         }
 
-        if (-1 == nodeIndex) {
+        if (CHARACTER_NOT_FOUND_INDEX == nodeIndex) {
             // No node at this point to accept the word. Create one.
             final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
             final CharGroup newGroup = new CharGroup(
@@ -600,20 +614,18 @@
         return result >= 0 ? result : -result - 1;
     }
 
-    private static int CHARACTER_NOT_FOUND = -1;
-
     /**
      * Find the index of a char in a node, if it exists.
      *
      * @param node the node to search in.
      * @param character the character to search for.
-     * @return the position of the character if it's there, or CHARACTER_NOT_FOUND = -1 else.
+     * @return the position of the character if it's there, or CHARACTER_NOT_FOUND_INDEX = -1 else.
      */
     private static int findIndexOfChar(final Node node, int character) {
         final int insertionIndex = findInsertionIndex(node, character);
-        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND;
+        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND_INDEX;
         return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex
-                : CHARACTER_NOT_FOUND;
+                : CHARACTER_NOT_FOUND_INDEX;
     }
 
     /**
@@ -628,7 +640,7 @@
         CharGroup currentGroup;
         do {
             int indexOfGroup = findIndexOfChar(node, codePoints[index]);
-            if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
+            if (CHARACTER_NOT_FOUND_INDEX == indexOfGroup) return null;
             currentGroup = node.mData.get(indexOfGroup);
 
             if (codePoints.length - index < currentGroup.mChars.length) return null;
diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
index 93687e1..a446672 100644
--- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
+++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java
@@ -23,6 +23,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 
 public class AccountUtils {
     private AccountUtils() {
@@ -44,4 +45,22 @@
         }
         return retval;
     }
+
+    /**
+     * Get all device accounts having specified domain name.
+     * @param context application context
+     * @param domain domain name used for filtering
+     * @return List of account names that contain the specified domain name
+     */
+    public static List<String> getDeviceAccountsWithDomain(
+            final Context context, final String domain) {
+        final ArrayList<String> retval = new ArrayList<String>();
+        final String atDomain = "@" + domain.toLowerCase(Locale.ROOT);
+        for (final Account account : getAccounts(context)) {
+            if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) {
+                retval.add(account.name);
+            }
+        }
+        return retval;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
similarity index 73%
rename from java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
rename to java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 5280283..9d041f4 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 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,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.personalization;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -23,33 +23,40 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.ExpandableDictionary;
+import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
-import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
+import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
+import com.android.inputmethod.latin.settings.Settings;
+import com.android.inputmethod.latin.utils.ByteArrayWrapper;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils;
+import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
-import java.lang.ref.SoftReference;
 import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 
 /**
- * Locally gathers stats about the words user types and various other signals like auto-correction
- * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ * This class is a base class of a dictionary for the personalized prediction language model.
  */
-public final class UserHistoryDictionary extends ExpandableDictionary {
-    private static final String TAG = UserHistoryDictionary.class.getSimpleName();
-    private static final String NAME = UserHistoryDictionary.class.getSimpleName();
+public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
+    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+        // TODO: Implement
+    }
+
+    private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
-    public static final boolean DBG_STRESS_TEST = false;
-    public static final boolean DBG_ALWAYS_WRITE = false;
-    public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
+    private static final boolean DBG_STRESS_TEST = false;
+    private static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG;
 
     private static final FormatOptions VERSION3 = new FormatOptions(3,
             true /* supportsDynamicUpdate */);
@@ -58,14 +65,7 @@
     private static final int FREQUENCY_FOR_TYPED = 2;
 
     /** Maximum number of pairs. Pruning will start when databases goes above this number. */
-    public static final int MAX_HISTORY_BIGRAMS = 10000;
-
-    /**
-     * When it hits maximum bigram pair, it will delete until you are left with
-     * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs.
-     * Do not keep this number small to avoid deleting too often.
-     */
-    public static final int DELETE_HISTORY_BIGRAMS = 1000;
+    private static final int MAX_HISTORY_BIGRAMS = 10000;
 
     /** Locale for which this user history dictionary is storing words */
     private final String mLocale;
@@ -78,30 +78,9 @@
     // Should always be false except when we use this class for test
     @UsedForTesting boolean isTest = false;
 
-    private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>>
-            sLangDictCache = CollectionUtils.newConcurrentHashMap();
-
-    public static synchronized UserHistoryDictionary getInstance(
-            final Context context, final String locale, final SharedPreferences sp) {
-        if (sLangDictCache.containsKey(locale)) {
-            final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale);
-            final UserHistoryDictionary dict = ref == null ? null : ref.get();
-            if (dict != null) {
-                if (PROFILE_SAVE_RESTORE) {
-                    Log.w(TAG, "Use cached UserHistoryDictionary for " + locale);
-                }
-                return dict;
-            }
-        }
-        final UserHistoryDictionary dict =
-                new UserHistoryDictionary(context, locale, sp);
-        sLangDictCache.put(locale, new SoftReference<UserHistoryDictionary>(dict));
-        return dict;
-    }
-
-    private UserHistoryDictionary(final Context context, final String locale,
-            final SharedPreferences sp) {
-        super(context, Dictionary.TYPE_USER_HISTORY);
+    /* package */ DynamicPredictionDictionaryBase(final Context context, final String locale,
+            final SharedPreferences sp, final String dictionaryType) {
+        super(context, dictionaryType);
         mLocale = locale;
         mPrefs = sp;
         if (mLocale != null && mLocale.length() > 1) {
@@ -116,8 +95,8 @@
         // Also, the database is written to somewhat frequently, so it needs to be kept alive
         // throughout the life of the process.
         // mOpenHelper.close();
-        // Ignore close because we cache UserHistoryDictionary for each language. See getInstance()
-        // above.
+        // Ignore close because we cache PersonalizationPredictionDictionary for each language.
+        // See getInstance() above.
         // super.close();
     }
 
@@ -147,8 +126,8 @@
      * The second word may not be null (a NullPointerException would be thrown).
      */
     public int addToUserHistory(final String word1, final String word2, final boolean isValid) {
-        if (word2.length() >= Constants.Dictionary.MAX_WORD_LENGTH ||
-                (word1 != null && word1.length() >= Constants.Dictionary.MAX_WORD_LENGTH)) {
+        if (word2.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (word1 != null && word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
             return -1;
         }
         if (mBigramListLock.tryLock()) {
@@ -198,7 +177,7 @@
     }
 
     @Override
-    public void loadDictionaryAsync() {
+    public final void loadDictionaryAsync() {
         // This must be run on non-main thread
         mBigramListLock.lock();
         try {
@@ -208,48 +187,47 @@
         }
     }
 
-    private int profTotal;
-
     private void loadDictionaryAsyncLocked() {
+        final int[] profTotalCount = { 0 };
+        final String locale = getLocale();
         if (DBG_STRESS_TEST) {
             try {
-                Log.w(TAG, "Start stress in loading: " + mLocale);
+                Log.w(TAG, "Start stress in loading: " + locale);
                 Thread.sleep(15000);
                 Log.w(TAG, "End stress in loading");
             } catch (InterruptedException e) {
             }
         }
-        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, mLocale);
+        final long last = Settings.readLastUserHistoryWriteTime(mPrefs, locale);
         final boolean initializing = last == 0;
         final long now = System.currentTimeMillis();
-        profTotal = 0;
-        final String fileName = NAME + "." + mLocale + ".dict";
+        final String fileName = getDictionaryFileName();
         final ExpandableDictionary dictionary = this;
         final OnAddWordListener listener = new OnAddWordListener() {
             @Override
             public void setUnigram(final String word, final String shortcutTarget,
                     final int frequency) {
-                profTotal++;
                 if (DBG_SAVE_RESTORE) {
                     Log.d(TAG, "load unigram: " + word + "," + frequency);
                 }
                 dictionary.addWord(word, shortcutTarget, frequency);
-                mBigramList.addBigram(null, word, (byte)frequency);
+                ++profTotalCount[0];
+                addToBigramListLocked(null, word, (byte)frequency);
             }
 
             @Override
             public void setBigram(final String word1, final String word2, final int frequency) {
-                if (word1.length() < Constants.Dictionary.MAX_WORD_LENGTH
-                        && word2.length() < Constants.Dictionary.MAX_WORD_LENGTH) {
-                    profTotal++;
+                if (word1.length() < Constants.DICTIONARY_MAX_WORD_LENGTH
+                        && word2.length() < Constants.DICTIONARY_MAX_WORD_LENGTH) {
                     if (DBG_SAVE_RESTORE) {
                         Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency);
                     }
+                    ++profTotalCount[0];
                     dictionary.setBigramAndGetFrequency(
                             word1, word2, initializing ? new ForgettingCurveParams(true)
                             : new ForgettingCurveParams(frequency, now, last));
                 }
-                mBigramList.addBigram(word1, word2, (byte)frequency);
+                addToBigramListLocked(word1, word2, (byte)frequency);
             }
         };
 
@@ -261,7 +239,7 @@
             inStream = new FileInputStream(file);
             inStream.read(buffer);
             UserHistoryDictIOUtils.readDictionaryBinary(
-                    new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+                    new ByteArrayWrapper(buffer), listener);
         } catch (FileNotFoundException e) {
             // This is an expected condition: we don't have a user history dictionary for this
             // language yet. It will be created sometime later.
@@ -278,11 +256,21 @@
             if (PROFILE_SAVE_RESTORE) {
                 final long diff = System.currentTimeMillis() - now;
                 Log.d(TAG, "PROF: Load UserHistoryDictionary: "
-                        + mLocale + ", " + diff + "ms. load " + profTotal + "entries.");
+                        + locale + ", " + diff + "ms. load " + profTotalCount[0] + "entries.");
             }
         }
     }
 
+    protected abstract String getDictionaryFileName();
+
+    protected String getLocale() {
+        return mLocale;
+    }
+
+    private void addToBigramListLocked(String word0, String word1, byte fcValue) {
+        mBigramList.addBigram(word0, word1, fcValue);
+    }
+
     /**
      * Async task to write pending words to the binarydicts.
      */
@@ -291,16 +279,16 @@
         private final UserHistoryDictionaryBigramList mBigramList;
         private final boolean mAddLevel0Bigrams;
         private final String mLocale;
-        private final UserHistoryDictionary mUserHistoryDictionary;
+        private final DynamicPredictionDictionaryBase mDynamicPredictionDictionary;
         private final SharedPreferences mPrefs;
         private final Context mContext;
 
         public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites,
-                final String locale, final UserHistoryDictionary dict,
+                final String locale, final DynamicPredictionDictionaryBase dict,
                 final SharedPreferences prefs, final Context context) {
             mBigramList = pendingWrites;
             mLocale = locale;
-            mUserHistoryDictionary = dict;
+            mDynamicPredictionDictionary = dict;
             mPrefs = prefs;
             mContext = context;
             mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS;
@@ -308,16 +296,20 @@
 
         @Override
         protected Void doInBackground(final Void... v) {
-            if (mUserHistoryDictionary.isTest) {
+            if (mDynamicPredictionDictionary.isTest) {
                 // If isTest == true, wait until the lock is released.
-                mUserHistoryDictionary.mBigramListLock.lock();
+                mDynamicPredictionDictionary.mBigramListLock.lock();
                 try {
                     doWriteTaskLocked();
                 } finally {
-                    mUserHistoryDictionary.mBigramListLock.unlock();
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
                 }
-            } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
-                doWriteTaskLocked();
+            } else if (mDynamicPredictionDictionary.mBigramListLock.tryLock()) {
+                try {
+                    doWriteTaskLocked();
+                } finally {
+                    mDynamicPredictionDictionary.mBigramListLock.unlock();
+                }
             }
             return null;
         }
@@ -334,7 +326,8 @@
             }
 
             final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0;
-            final String fileName = NAME + "." + mLocale + ".dict";
+            final String fileName =
+                    mDynamicPredictionDictionary.getDictionaryFileName();
             final File file = new File(mContext.getFilesDir(), fileName);
             FileOutputStream out = null;
 
@@ -370,7 +363,8 @@
                 freq = FREQUENCY_FOR_TYPED;
                 final byte prevFc = mBigramList.getBigrams(word1).get(word2);
             } else { // bigram
-                final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2);
+                final NextWord nw =
+                        mDynamicPredictionDictionary.getBigramWord(word1, word2);
                 if (nw != null) {
                     final ForgettingCurveParams fcp = nw.getFcParams();
                     final byte prevFc = mBigramList.getBigrams(word1).get(word2);
@@ -395,7 +389,8 @@
     }
 
     @UsedForTesting
-    void forceAddWordForTest(final String word1, final String word2, final boolean isValid) {
+    /* package for test */ void forceAddWordForTest(
+            final String word1, final String word2, final boolean isValid) {
         mBigramListLock.lock();
         try {
             addToUserHistory(word1, word2, isValid);
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
new file mode 100644
index 0000000..19554d6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -0,0 +1,61 @@
+/*
+ * 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.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
+
+import android.content.Context;
+
+/**
+ * This class is a dictionary for the personalized language model that uses binary dictionary.
+ */
+public class PersonalizationDictionary extends ExpandableBinaryDictionary {
+    private static final String NAME = "personalization";
+
+    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+        // TODO: Implement
+    }
+
+    /** Locale for which this user history dictionary is storing words */
+    private final String mLocale;
+
+    // Singleton
+    private PersonalizationDictionary(final Context context, final String locale) {
+        super(context, getFilenameWithLocale(NAME, locale), Dictionary.TYPE_PERSONALIZATION);
+        mLocale = locale;
+    }
+
+    @Override
+    protected void loadDictionaryAsync() {
+        // TODO: Implement
+    }
+
+    @Override
+    protected boolean hasContentChanged() {
+        // TODO: Implement
+        return false;
+    }
+
+    @Override
+    protected boolean needsToReloadBeforeWriting() {
+        // TODO: Implement
+        return false;
+    }
+
+    // TODO: Implement
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
new file mode 100644
index 0000000..9f013df
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -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.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import java.lang.ref.SoftReference;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class PersonalizationDictionaryHelper {
+    private static final String TAG = PersonalizationDictionaryHelper.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final ConcurrentHashMap<String, SoftReference<UserHistoryPredictionDictionary>>
+            sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap();
+
+    private static final ConcurrentHashMap<String,
+            SoftReference<PersonalizationPredictionDictionary>>
+                    sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap();
+
+    public static UserHistoryPredictionDictionary getUserHistoryPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangUserHistoryDictCache) {
+            if (sLangUserHistoryDictCache.containsKey(locale)) {
+                final SoftReference<UserHistoryPredictionDictionary> ref =
+                        sLangUserHistoryDictCache.get(locale);
+                final UserHistoryPredictionDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached UserHistoryPredictionDictionary for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final UserHistoryPredictionDictionary dict =
+                    new UserHistoryPredictionDictionary(context, locale, sp);
+            sLangUserHistoryDictCache.put(
+                    locale, new SoftReference<UserHistoryPredictionDictionary>(dict));
+            return dict;
+        }
+    }
+
+    public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
+            final Context context, final String locale, final SharedPreferences sp) {
+        synchronized (sLangPersonalizationDictCache) {
+            if (sLangPersonalizationDictCache.containsKey(locale)) {
+                final SoftReference<PersonalizationPredictionDictionary> ref =
+                        sLangPersonalizationDictCache.get(locale);
+                final PersonalizationPredictionDictionary dict = ref == null ? null : ref.get();
+                if (dict != null) {
+                    if (DEBUG) {
+                        Log.w(TAG, "Use cached PersonalizationPredictionDictionary for " + locale);
+                    }
+                    return dict;
+                }
+            }
+            final PersonalizationPredictionDictionary dict =
+                    new PersonalizationPredictionDictionary(context, locale, sp);
+            sLangPersonalizationDictCache.put(
+                    locale, new SoftReference<PersonalizationPredictionDictionary>(dict));
+            return dict;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
new file mode 100644
index 0000000..c78e5a9
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
@@ -0,0 +1,21 @@
+/*
+ * 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.personalization;
+
+public interface PersonalizationDictionaryUpdateListener {
+    // TODO: Implement
+}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
new file mode 100644
index 0000000..955bd27
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationPredictionDictionary.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PersonalizationPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = PersonalizationPredictionDictionary.class.getSimpleName();
+
+    /* package */ PersonalizationPredictionDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, locale, sp, Dictionary.TYPE_PERSONALIZATION_PREDICTION_IN_JAVA);
+    }
+
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
similarity index 93%
rename from java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
rename to java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
index 316f096..f21db25 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryBigramList.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.personalization;
 
 import android.util.Log;
 
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 import java.util.Set;
@@ -52,7 +53,7 @@
      * Called when loaded from the SQL DB.
      */
     public void addBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- add bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
@@ -72,7 +73,7 @@
      * Called when inserted to the SQL DB.
      */
     public void updateBigram(String word1, String word2, byte fcValue) {
-        if (UserHistoryDictionary.DBG_SAVE_RESTORE) {
+        if (UserHistoryPredictionDictionary.DBG_SAVE_RESTORE) {
             Log.d(TAG, "--- update bigram: " + word1 + ", " + word2 + ", " + fcValue);
         }
         final HashMap<String, Byte> map;
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
new file mode 100644
index 0000000..d117844
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryPredictionDictionary.java
@@ -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.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import com.android.inputmethod.latin.Dictionary;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+/**
+ * Locally gathers stats about the words user types and various other signals like auto-correction
+ * cancellation or manual picks. This allows the keyboard to adapt to the typist over time.
+ */
+public class UserHistoryPredictionDictionary extends DynamicPredictionDictionaryBase {
+    private static final String NAME = UserHistoryPredictionDictionary.class.getSimpleName();
+    /* package */ UserHistoryPredictionDictionary(final Context context, final String locale,
+            final SharedPreferences sp) {
+        super(context, locale, sp, Dictionary.TYPE_USER_HISTORY);
+    }
+
+    @Override
+    protected String getDictionaryFileName() {
+        return NAME + "." + getLocale() + ".dict";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
new file mode 100644
index 0000000..139f5e2
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalFeaturesSettingUtils.java
@@ -0,0 +1,47 @@
+/*
+ * 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.settings;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import com.android.inputmethodcommon.InputMethodSettingsFragment;
+
+/**
+ * Utility class for managing additional features settings.
+ */
+public class AdditionalFeaturesSettingUtils {
+    public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0;
+
+    private AdditionalFeaturesSettingUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void addAdditionalFeaturesPreferences(
+            final Context context, final InputMethodSettingsFragment settingsFragment) {
+        // do nothing.
+    }
+
+    public static void readAdditionalFeaturesPreferencesIntoArray(
+            final SharedPreferences prefs, final int[] additionalFeaturesPreferences) {
+        // do nothing.
+    }
+
+    public static int[] getAdditionalNativeSuggestOptions() {
+        return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
similarity index 93%
rename from java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
rename to java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
index ff5e339..4bf524c 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.ASCII_CAPABLE;
 
@@ -44,6 +44,13 @@
 import android.widget.SpinnerAdapter;
 import android.widget.Toast;
 
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.IntentUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+
 import java.util.ArrayList;
 import java.util.TreeSet;
 
@@ -71,7 +78,7 @@
 
         public SubtypeLocaleItem(final String localeString) {
             this(localeString,
-                    SubtypeLocale.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
+                    SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(localeString));
         }
 
         @Override
@@ -102,7 +109,7 @@
                 if (DEBUG_SUBTYPE_ID) {
                     android.util.Log.d(TAG, String.format("%-6s 0x%08x %11d %s",
                             subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
-                            SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype)));
+                            SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
                 }
                 if (subtype.containsExtraValueKey(ASCII_CAPABLE)) {
                     items.add(createItem(context, subtype.getLocale()));
@@ -114,7 +121,7 @@
 
         public static SubtypeLocaleItem createItem(final Context context,
                 final String localeString) {
-            if (localeString.equals(SubtypeLocale.NO_LANGUAGE)) {
+            if (localeString.equals(SubtypeLocaleUtils.NO_LANGUAGE)) {
                 final String displayName = context.getString(R.string.subtype_no_language);
                 return new SubtypeLocaleItem(localeString, displayName);
             } else {
@@ -125,8 +132,8 @@
 
     static final class KeyboardLayoutSetItem extends Pair<String, String> {
         public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
-            super(SubtypeLocale.getKeyboardLayoutSetName(subtype),
-                    SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype));
+            super(SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype),
+                    SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype));
         }
 
         @Override
@@ -141,10 +148,10 @@
             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
 
             // TODO: Should filter out already existing combinations of locale and layout.
-            for (final String layout : SubtypeLocale.getPredefinedKeyboardLayoutSet()) {
+            for (final String layout : SubtypeLocaleUtils.getPredefinedKeyboardLayoutSet()) {
                 // This is a dummy subtype with NO_LANGUAGE, only for display.
-                final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(
-                        SubtypeLocale.NO_LANGUAGE, layout, null);
+                final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
+                        SubtypeLocaleUtils.NO_LANGUAGE, layout, null);
                 add(new KeyboardLayoutSetItem(subtype));
             }
         }
@@ -205,11 +212,11 @@
                 setKey(KEY_NEW_SUBTYPE);
             } else {
                 final String displayName =
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype);
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
                 setTitle(displayName);
                 setDialogTitle(displayName);
                 setKey(KEY_PREFIX + subtype.getLocale() + "_"
-                        + SubtypeLocale.getKeyboardLayoutSetName(subtype));
+                        + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
             }
         }
 
@@ -279,7 +286,7 @@
                         (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
                 final KeyboardLayoutSetItem layout =
                         (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
-                final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(
+                final InputMethodSubtype subtype = AdditionalSubtypeUtils.createAdditionalSubtype(
                         locale.first, layout.first, ASCII_CAPABLE);
                 setSubtype(subtype);
                 notifyChanged();
@@ -497,13 +504,13 @@
         final Context context = getActivity();
         final Resources res = context.getResources();
         final String message = res.getString(R.string.custom_input_style_already_exists,
-                SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
+                SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype));
         Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
     }
 
     private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) {
         final String localeString = subtype.getLocale();
-        final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+        final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
         return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
                 localeString, keyboardLayoutSetName);
     }
@@ -536,7 +543,7 @@
         final PreferenceGroup group = getPreferenceScreen();
         group.removeAll();
         final InputMethodSubtype[] subtypesArray =
-                AdditionalSubtype.createAdditionalSubtypesArray(prefSubtypes);
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes);
         for (final InputMethodSubtype subtype : subtypesArray) {
             final SubtypePreference pref = new SubtypePreference(
                     context, subtype, mSubtypeProxy);
@@ -565,7 +572,7 @@
         super.onPause();
         final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources());
         final InputMethodSubtype[] subtypes = getSubtypes();
-        final String prefSubtypes = AdditionalSubtype.createPrefSubtypes(subtypes);
+        final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes);
         if (prefSubtypes.equals(oldSubtypes)) {
             return;
         }
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
similarity index 88%
rename from java/src/com/android/inputmethod/latin/DebugSettings.java
rename to java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 5969a63..b1cd887 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -14,31 +14,31 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
-import android.content.Context;
 import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Bundle;
 import android.os.Process;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceScreen;
-import android.util.Log;
 
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.research.ResearchLogger;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 public final class DebugSettings extends PreferenceFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final String TAG = DebugSettings.class.getSimpleName();
 
     public static final String PREF_DEBUG_MODE = "debug_mode";
     public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch";
     public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
     public static final String PREF_STATISTICS_LOGGING = "enable_logging";
+    public static final String PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG =
+            "use_only_personalization_dictionary_for_debug";
     private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary";
     private static final boolean SHOW_STATISTICS_LOGGING = false;
 
@@ -68,7 +68,7 @@
             }
         }
 
-        PreferenceScreen readExternalDictionary =
+        final PreferenceScreen readExternalDictionary =
                 (PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY);
         if (null != readExternalDictionary) {
             readExternalDictionary.setOnPreferenceClickListener(
@@ -113,6 +113,8 @@
         } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)
                 || key.equals(KeyboardSwitcher.PREF_KEYBOARD_LAYOUT)) {
             mServiceNeedsRestart = true;
+        } else if (key.equals(PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG)) {
+            mServiceNeedsRestart = true;
         }
     }
 
@@ -122,7 +124,7 @@
         }
         boolean isDebugMode = mDebugMode.isChecked();
         final String version = getResources().getString(
-                R.string.version_text, Utils.getVersionName(getActivity()));
+                R.string.version_text, ApplicationUtils.getVersionName(getActivity()));
         if (!isDebugMode) {
             mDebugMode.setTitle(version);
             mDebugMode.setSummary("");
diff --git a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
similarity index 93%
rename from java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
rename to java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
index e1b5a80..b499c26 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettingsActivity.java
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.preference.PreferenceActivity;
 
+import com.android.inputmethod.latin.R;
+
 public final class DebugSettingsActivity extends PreferenceActivity {
     private static final String DEFAULT_FRAGMENT = DebugSettings.class.getName();
 
diff --git a/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
new file mode 100644
index 0000000..878c505
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/NativeSuggestOptions.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+public class NativeSuggestOptions {
+    // Need to update suggest_options.h when you add, remove or reorder options.
+    private static final int IS_GESTURE = 0;
+    private static final int USE_FULL_EDIT_DISTANCE = 1;
+    private static final int OPTIONS_SIZE = 2;
+
+    private final int[] mOptions = new int[OPTIONS_SIZE
+            + AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
+
+    public void setIsGesture(final boolean value) {
+        setBooleanOption(IS_GESTURE, value);
+    }
+
+    public void setUseFullEditDistance(final boolean value) {
+        setBooleanOption(USE_FULL_EDIT_DISTANCE, value);
+    }
+
+    public void setAdditionalFeaturesOptions(final int[] additionalOptions) {
+        for (int i = 0; i < additionalOptions.length; i++) {
+            setIntegerOption(OPTIONS_SIZE + i, additionalOptions[i]);
+        }
+    }
+
+    public int[] getOptions() {
+        return mOptions;
+    }
+
+    private void setBooleanOption(final int key, final boolean value) {
+        mOptions[key] = value ? 1 : 0;
+    }
+
+    private void setIntegerOption(final int key, final int value) {
+        mOptions[key] = value;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java b/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java
similarity index 78%
rename from java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
rename to java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java
index 7c4156c..802574a 100644
--- a/java/src/com/android/inputmethod/latin/SeekBarDialogPreference.java
+++ b/java/src/com/android/inputmethod/latin/settings/SeekBarDialogPreference.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.app.AlertDialog;
 import android.content.Context;
@@ -26,16 +26,19 @@
 import android.widget.SeekBar;
 import android.widget.TextView;
 
+import com.android.inputmethod.latin.R;
+
 public final class SeekBarDialogPreference extends DialogPreference
         implements SeekBar.OnSeekBarChangeListener {
     public interface ValueProxy {
         public int readValue(final String key);
         public int readDefaultValue(final String key);
         public void writeValue(final int value, final String key);
+        public void writeDefaultValue(final String key);
+        public String getValueText(final int value);
         public void feedbackValue(final int value);
     }
 
-    private final int mValueFormatResId;
     private final int mMaxValue;
     private final int mMinValue;
     private final int mStepValue;
@@ -49,7 +52,6 @@
         super(context, attrs);
         final TypedArray a = context.obtainStyledAttributes(
                 attrs, R.styleable.SeekBarDialogPreference, 0, 0);
-        mValueFormatResId = a.getResourceId(R.styleable.SeekBarDialogPreference_valueFormatText, 0);
         mMaxValue = a.getInt(R.styleable.SeekBarDialogPreference_maxValue, 0);
         mMinValue = a.getInt(R.styleable.SeekBarDialogPreference_minValue, 0);
         mStepValue = a.getInt(R.styleable.SeekBarDialogPreference_stepValue, 0);
@@ -59,15 +61,8 @@
 
     public void setInterface(final ValueProxy proxy) {
         mValueProxy = proxy;
-        setSummary(getValueText(clipValue(proxy.readValue(getKey()))));
-    }
-
-    private String getValueText(final int value) {
-        if (mValueFormatResId == 0) {
-            return Integer.toString(value);
-        } else {
-            return getContext().getString(mValueFormatResId, value);
-        }
+        final int value = mValueProxy.readValue(getKey());
+        setSummary(mValueProxy.getValueText(value));
     }
 
     @Override
@@ -100,16 +95,11 @@
         return clipValue(getValueFromProgress(progress));
     }
 
-    private void setValue(final int value, final boolean fromUser) {
-        mValueView.setText(getValueText(value));
-        if (!fromUser) {
-            mSeekBar.setProgress(getProgressFromValue(value));
-        }
-    }
-
     @Override
     protected void onBindDialogView(final View view) {
-        setValue(clipValue(mValueProxy.readValue(getKey())), false /* fromUser */);
+        final int value = mValueProxy.readValue(getKey());
+        mValueView.setText(mValueProxy.getValueText(value));
+        mSeekBar.setProgress(getProgressFromValue(clipValue(value)));
     }
 
     @Override
@@ -122,19 +112,29 @@
     @Override
     public void onClick(final DialogInterface dialog, final int which) {
         super.onClick(dialog, which);
+        final String key = getKey();
         if (which == DialogInterface.BUTTON_NEUTRAL) {
-            setValue(clipValue(mValueProxy.readDefaultValue(getKey())), false /* fromUser */);
+            final int value = mValueProxy.readDefaultValue(key);
+            setSummary(mValueProxy.getValueText(value));
+            mValueProxy.writeDefaultValue(key);
+            return;
         }
-        if (which != DialogInterface.BUTTON_NEGATIVE) {
-            setSummary(mValueView.getText());
-            mValueProxy.writeValue(getClippedValueFromProgress(mSeekBar.getProgress()), getKey());
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            final int value = getClippedValueFromProgress(mSeekBar.getProgress());
+            setSummary(mValueProxy.getValueText(value));
+            mValueProxy.writeValue(value, key);
+            return;
         }
     }
 
     @Override
     public void onProgressChanged(final SeekBar seekBar, final int progress,
             final boolean fromUser) {
-        setValue(getClippedValueFromProgress(progress), fromUser);
+        final int value = getClippedValueFromProgress(progress);
+        mValueView.setText(mValueProxy.getValueText(value));
+        if (!fromUser) {
+            mSeekBar.setProgress(getProgressFromValue(value));
+        }
     }
 
     @Override
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
similarity index 85%
rename from java/src/com/android/inputmethod/latin/Settings.java
rename to java/src/com/android/inputmethod/latin/settings/Settings.java
index 9fefb58..d432087 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -14,20 +14,30 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.preference.PreferenceManager;
+import android.util.Log;
 
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
+import com.android.inputmethod.latin.InputAttributes;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.DebugLogUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
 
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.concurrent.locks.ReentrantLock;
 
 public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final String TAG = Settings.class.getSimpleName();
     // In the same order as xml/prefs.xml
     public static final String PREF_GENERAL_SETTINGS = "general_settings";
     public static final String PREF_AUTO_CAP = "auto_cap";
@@ -85,8 +95,8 @@
 
     private Resources mRes;
     private SharedPreferences mPrefs;
-    private Locale mCurrentLocale;
     private SettingsValues mSettingsValues;
+    private final ReentrantLock mSettingsValuesLock = new ReentrantLock();
 
     private static final Settings sInstance = new Settings();
 
@@ -114,19 +124,34 @@
 
     @Override
     public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
-        loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes);
+        mSettingsValuesLock.lock();
+        try {
+            if (mSettingsValues == null) {
+                // TODO: Introduce a static function to register this class and ensure that
+                // loadSettings must be called before "onSharedPreferenceChanged" is called.
+                Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
+                return;
+            }
+            loadSettings(mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
+        } finally {
+            mSettingsValuesLock.unlock();
+        }
     }
 
     public void loadSettings(final Locale locale, final InputAttributes inputAttributes) {
-        mCurrentLocale = locale;
-        final SharedPreferences prefs = mPrefs;
-        final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
-            @Override
-            protected SettingsValues job(final Resources res) {
-                return new SettingsValues(prefs, res, inputAttributes);
-            }
-        };
-        mSettingsValues = job.runInLocale(mRes, locale);
+        mSettingsValuesLock.lock();
+        try {
+            final SharedPreferences prefs = mPrefs;
+            final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
+                @Override
+                protected SettingsValues job(final Resources res) {
+                    return new SettingsValues(prefs, locale, res, inputAttributes);
+                }
+            };
+            mSettingsValues = job.runInLocale(mRes, locale);
+        } finally {
+            mSettingsValuesLock.unlock();
+        }
     }
 
     // TODO: Remove this method and add proxy method to SettingsValues.
@@ -142,8 +167,8 @@
         return mSettingsValues.mWordSeparators;
     }
 
-    public Locale getCurrentLocale() {
-        return mCurrentLocale;
+    public boolean isWordSeparator(final int code) {
+        return mSettingsValues.isWordSeparator(code);
     }
 
     public boolean getBlockPotentiallyOffensive() {
@@ -223,7 +248,7 @@
 
     public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
             final Resources res) {
-        final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes(
+        final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
                 res.getStringArray(R.array.predefined_subtypes));
         return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes);
     }
@@ -312,4 +337,10 @@
     public static boolean isInternal(final SharedPreferences prefs) {
         return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
     }
+
+    public static boolean readUseOnlyPersonalizationDictionaryForDebug(
+            final SharedPreferences prefs) {
+        return prefs.getBoolean(
+                DebugSettings.PREF_USE_ONLY_PERSONALIZATION_DICTIONARY_FOR_DEBUG, false);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsActivity.java b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
similarity index 95%
rename from java/src/com/android/inputmethod/latin/SettingsActivity.java
rename to java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
index 37ac2e3..6c38186 100644
--- a/java/src/com/android/inputmethod/latin/SettingsActivity.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.content.Intent;
 import android.preference.PreferenceActivity;
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
similarity index 82%
rename from java/src/com/android/inputmethod/latin/SettingsFragment.java
rename to java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 835ef7b..4467777 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.app.Activity;
 import android.app.backup.BackupManager;
@@ -25,6 +25,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
@@ -32,20 +33,33 @@
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
+import android.util.Log;
 import android.view.inputmethod.InputMethodSubtype;
 
-import java.util.TreeSet;
-
 import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
 import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
+import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
+import com.android.inputmethod.latin.utils.FeedbackUtils;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
+import com.android.inputmethod.research.ResearchLogger;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
+import java.util.TreeSet;
+
 public final class SettingsFragment extends InputMethodSettingsFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final boolean DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS = false;
+    private static final String TAG = SettingsFragment.class.getSimpleName();
+    private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
+    private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS =
+            DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
+                    || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */;
 
     private ListPreference mVoicePreference;
     private ListPreference mShowCorrectionSuggestionsPreference;
@@ -80,7 +94,7 @@
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
             preferenceScreen.setTitle(
-                    Utils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
+                    ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
         }
 
         final Resources res = getResources();
@@ -90,7 +104,7 @@
         // singleton and utility classes may not have been initialized.  We have to call
         // initialization method of these classes here. See {@link LatinIME#onCreate()}.
         SubtypeSwitcher.init(context);
-        SubtypeLocale.init(context);
+        SubtypeLocaleUtils.init(context);
         AudioAndHapticFeedbackManager.init(context);
 
         mVoicePreference = (ListPreference) findPreference(Settings.PREF_VOICE_MODE);
@@ -128,7 +142,12 @@
                 feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                     @Override
                     public boolean onPreferenceClick(final Preference pref) {
-                        FeedbackUtils.showFeedbackForm(getActivity());
+                        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+                            // Use development-only feedback mechanism
+                            ResearchLogger.getInstance().presentFeedbackDialogFromSettings();
+                        } else {
+                            FeedbackUtils.showFeedbackForm(getActivity());
+                        }
                         return true;
                     }
                 });
@@ -139,6 +158,10 @@
                 miscSettings.removePreference(aboutSettings);
             }
         }
+        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
+            // The about screen contains items that may be confusing in development-only versions.
+            miscSettings.removePreference(aboutSettings);
+        }
 
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
@@ -190,23 +213,24 @@
         final Intent intent = dictionaryLink.getIntent();
         intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
         final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
-        // TODO: The development-only-diagnostic version is not supported by the Dictionary Pack
-        // Service yet
-        if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS || 0 >= number) {
+        if (0 >= number) {
             textCorrectionGroup.removePreference(dictionaryLink);
         }
 
         final Preference editPersonalDictionary =
                 findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
         final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
-        final ResolveInfo ri = context.getPackageManager().resolveActivity(
-                editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
-        if (DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS || ri == null) {
-            updateUserDictionaryPreference(editPersonalDictionary);
+        final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS ? null
+                : context.getPackageManager().resolveActivity(
+                        editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (ri == null) {
+            overwriteUserDictionaryPreference(editPersonalDictionary);
         }
 
         if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
             removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
+        } else {
+            AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
         }
 
         setupKeyLongpressTimeoutSettings(prefs, res);
@@ -244,7 +268,14 @@
 
     @Override
     public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
-        (new BackupManager(getActivity())).dataChanged();
+        final Activity activity = getActivity();
+        if (activity == null) {
+            // TODO: Introduce a static function to register this class and ensure that
+            // onCreate must be called before "onSharedPreferenceChanged" is called.
+            Log.w(TAG, "onSharedPreferenceChanged called before activity starts.");
+            return;
+        }
+        (new BackupManager(activity)).dataChanged();
         final Resources res = getResources();
         if (key.equals(Settings.PREF_POPUP_ON)) {
             setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
@@ -283,11 +314,11 @@
         final Resources res = getResources();
         final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
         final InputMethodSubtype[] subtypes =
-                AdditionalSubtype.createAdditionalSubtypesArray(prefSubtype);
+                AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
         final StringBuilder styles = new StringBuilder();
         for (final InputMethodSubtype subtype : subtypes) {
             if (styles.length() > 0) styles.append(", ");
-            styles.append(SubtypeLocale.getSubtypeDisplayNameInSystemLocale(subtype));
+            styles.append(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype));
         }
         customInputStyles.setSummary(styles);
     }
@@ -327,6 +358,11 @@
             }
 
             @Override
+            public void writeDefaultValue(final String key) {
+                sp.edit().remove(key).apply();
+            }
+
+            @Override
             public int readValue(final String key) {
                 return Settings.readKeypressVibrationDuration(sp, res);
             }
@@ -340,6 +376,14 @@
             public void feedbackValue(final int value) {
                 AudioAndHapticFeedbackManager.getInstance().vibrate(value);
             }
+
+            @Override
+            public String getValueText(final int value) {
+                if (value < 0) {
+                    return res.getString(R.string.settings_system_default);
+                }
+                return res.getString(R.string.abbreviation_unit_milliseconds, value);
+            }
         });
     }
 
@@ -357,6 +401,11 @@
             }
 
             @Override
+            public void writeDefaultValue(final String key) {
+                sp.edit().remove(key).apply();
+            }
+
+            @Override
             public int readValue(final String key) {
                 return Settings.readKeyLongpressTimeout(sp, res);
             }
@@ -367,6 +416,11 @@
             }
 
             @Override
+            public String getValueText(final int value) {
+                return res.getString(R.string.abbreviation_unit_milliseconds, value);
+            }
+
+            @Override
             public void feedbackValue(final int value) {}
         });
     }
@@ -395,6 +449,11 @@
             }
 
             @Override
+            public void writeDefaultValue(final String key) {
+                sp.edit().remove(key).apply();
+            }
+
+            @Override
             public int readValue(final String key) {
                 return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
             }
@@ -405,6 +464,14 @@
             }
 
             @Override
+            public String getValueText(final int value) {
+                if (value < 0) {
+                    return res.getString(R.string.settings_system_default);
+                }
+                return Integer.toString(value);
+            }
+
+            @Override
             public void feedbackValue(final int value) {
                 am.playSoundEffect(
                         AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
@@ -412,7 +479,7 @@
         });
     }
 
-    private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
+    private void overwriteUserDictionaryPreference(Preference userDictionaryPreference) {
         final Activity activity = getActivity();
         final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
         if (null == localeList) {
diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
similarity index 91%
rename from java/src/com/android/inputmethod/latin/SettingsValues.java
rename to java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 615b2df..8aafb07 100644
--- a/java/src/com/android/inputmethod/latin/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.settings;
 
 import android.content.SharedPreferences;
 import android.content.res.Configuration;
@@ -23,14 +23,24 @@
 import android.view.inputmethod.EditorInfo;
 
 import com.android.inputmethod.keyboard.internal.KeySpecParser;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.InputAttributes;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.SubtypeSwitcher;
+import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Locale;
 
 /**
  * When you call the constructor of this class, you may want to change the current system locale by
- * using {@link LocaleUtils.RunInLocale}.
+ * using {@link com.android.inputmethod.latin.utils.RunInLocale}.
  */
 public final class SettingsValues {
     private static final String TAG = SettingsValues.class.getSimpleName();
@@ -65,6 +75,7 @@
     public final boolean mGestureFloatingPreviewTextEnabled;
     public final boolean mSlidingKeyInputPreviewEnabled;
     public final int mKeyLongpressTimeout;
+    public final Locale mLocale;
 
     // From the input box
     public final InputAttributes mInputAttributes;
@@ -80,11 +91,16 @@
     private final boolean mVoiceKeyEnabled;
     private final boolean mVoiceKeyOnMain;
 
+    // Setting values for additional features
+    public final int[] mAdditionalFeaturesSettingValues =
+            new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE];
+
     // Debug settings
     public final boolean mIsInternal;
 
-    public SettingsValues(final SharedPreferences prefs, final Resources res,
+    public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res,
             final InputAttributes inputAttributes) {
+        mLocale = locale;
         // Get the resources
         mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
         mSymbolsPrecededBySpace =
@@ -96,7 +112,7 @@
         mWordConnectors =
                 StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors));
         Arrays.sort(mWordConnectors);
-        final String[] suggestPuncsSpec = StringUtils.parseCsvString(res.getString(
+        final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString(
                 R.string.suggested_punctuations));
         mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec);
         mWordSeparators = res.getString(R.string.symbols_word_separators);
@@ -149,6 +165,8 @@
                 Settings.PREF_SHOW_SUGGESTIONS_SETTING,
                 res.getString(R.string.prefs_suggestion_visibility_default_value));
         mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting);
+        AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray(
+                prefs, mAdditionalFeaturesSettingValues);
         mIsInternal = Settings.isInternal(prefs);
     }
 
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
index 6a7cd9b..050d8d2 100644
--- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -25,10 +25,10 @@
 import android.os.Process;
 import android.preference.PreferenceManager;
 import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.inputmethod.compat.IntentCompatUtils;
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.Settings;
+import com.android.inputmethod.latin.settings.Settings;
 
 /**
  * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
@@ -65,17 +65,16 @@
         }
 
         // The process that hosts this broadcast receiver is invoked and remains alive even after
-        // 1) the package has been re-installed, 2) the device has been booted,
-        // 3) a multiuser has been created.
+        // 1) the package has been re-installed, 2) the device has just booted,
+        // 3) a new user has been created.
         // There is no good reason to keep the process alive if this IME isn't a current IME.
-        final boolean isCurrentImeOfCurrentUser;
-        if (RichInputMethodManager.isInputMethodManagerValidForUserOfThisProcess(context)) {
-            RichInputMethodManager.init(context);
-            isCurrentImeOfCurrentUser = SetupActivity.isThisImeCurrent(context);
-        } else {
-            isCurrentImeOfCurrentUser = false;
-        }
-
+        final InputMethodManager imm =
+                (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
+        // Called to check whether this IME has been triggered by the current user or not
+        final boolean isInputMethodManagerValidForUserOfThisProcess =
+                !imm.getInputMethodList().isEmpty();
+        final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess
+                && SetupActivity.isThisImeCurrent(context, imm);
         if (!isCurrentImeOfCurrentUser) {
             final int myPid = Process.myPid();
             Log.i(TAG, "Killing my process: pid=" + myPid);
@@ -91,7 +90,7 @@
         } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
             Log.i(TAG, "Boot has been completed");
             return true;
-        } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) {
+        } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) {
             Log.i(TAG, "User initialize");
             return true;
         }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index 8a2de88..a68f98f 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -24,8 +24,6 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
-import com.android.inputmethod.latin.RichInputMethodManager;
-
 public final class SetupActivity extends Activity {
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
@@ -40,17 +38,24 @@
         }
     }
 
+    /*
+     * We may not be able to get our own {@link InputMethodInfo} just after this IME is installed
+     * because {@link InputMethodManagerService} may not be aware of this IME yet.
+     * Note: {@link RichInputMethodManager} has similar methods. Here in setup wizard, we can't
+     * use it for the reason above.
+     */
+
     /**
      * Check if the IME specified by the context is enabled.
-     * Note that {@link RichInputMethodManager} must have been initialized before calling this
-     * method.
+     * CAVEAT: This may cause a round trip IPC.
      *
      * @param context package context of the IME to be checked.
+     * @param imm the {@link InputMethodManager}.
      * @return true if this IME is enabled.
      */
-    public static boolean isThisImeEnabled(final Context context) {
+    /* package */ static boolean isThisImeEnabled(final Context context,
+            final InputMethodManager imm) {
         final String packageName = context.getPackageName();
-        final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
         for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
             if (packageName.equals(imi.getPackageName())) {
                 return true;
@@ -61,17 +66,36 @@
 
     /**
      * Check if the IME specified by the context is the current IME.
-     * Note that {@link RichInputMethodManager} must have been initialized before calling this
-     * method.
+     * CAVEAT: This may cause a round trip IPC.
      *
      * @param context package context of the IME to be checked.
+     * @param imm the {@link InputMethodManager}.
      * @return true if this IME is the current IME.
      */
-    public static boolean isThisImeCurrent(final Context context) {
-        final InputMethodInfo myImi =
-                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+    /* package */ static boolean isThisImeCurrent(final Context context,
+            final InputMethodManager imm) {
+        final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm);
         final String currentImeId = Settings.Secure.getString(
                 context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
-        return myImi.getId().equals(currentImeId);
+        return imi != null && imi.getId().equals(currentImeId);
+    }
+
+    /**
+     * Get {@link InputMethodInfo} of the IME specified by the package name.
+     * CAVEAT: This may cause a round trip IPC.
+     *
+     * @param packageName package name of the IME.
+     * @param imm the {@link InputMethodManager}.
+     * @return the {@link InputMethodInfo} of the IME specified by the <code>packageName</code>,
+     * or null if not found.
+     */
+    /* package */ static InputMethodInfo getInputMethodInfoOf(final String packageName,
+            final InputMethodManager imm) {
+        for (final InputMethodInfo imi : imm.getInputMethodList()) {
+            if (packageName.equals(imi.getPackageName())) {
+                return imi;
+            }
+        }
+        return null;
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
index 78a6478..c4a813c 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java
@@ -28,17 +28,17 @@
 import android.util.Log;
 import android.view.View;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.ImageView;
 import android.widget.TextView;
 import android.widget.VideoView;
 
 import com.android.inputmethod.compat.TextViewCompatUtils;
 import com.android.inputmethod.compat.ViewCompatUtils;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.SettingsActivity;
-import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+import com.android.inputmethod.latin.settings.SettingsActivity;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 
 import java.util.ArrayList;
 
@@ -48,6 +48,8 @@
 
     private static final boolean ENABLE_WELCOME_VIDEO = true;
 
+    private InputMethodManager mImm;
+
     private View mSetupWizard;
     private View mWelcomeScreen;
     private View mSetupScreen;
@@ -69,15 +71,19 @@
     private static final int STEP_LAUNCHING_IME_SETTINGS = 4;
     private static final int STEP_BACK_FROM_IME_SETTINGS = 5;
 
-    final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+    private SettingsPoolingHandler mHandler;
 
-    static final class SettingsPoolingHandler
+    private static final class SettingsPoolingHandler
             extends StaticInnerHandlerWrapper<SetupWizardActivity> {
         private static final int MSG_POLLING_IME_SETTINGS = 0;
         private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
 
-        public SettingsPoolingHandler(final SetupWizardActivity outerInstance) {
+        private final InputMethodManager mImmInHandler;
+
+        public SettingsPoolingHandler(final SetupWizardActivity outerInstance,
+                final InputMethodManager imm) {
             super(outerInstance);
+            mImmInHandler = imm;
         }
 
         @Override
@@ -88,7 +94,7 @@
             }
             switch (msg.what) {
             case MSG_POLLING_IME_SETTINGS:
-                if (SetupActivity.isThisImeEnabled(setupWizardActivity)) {
+                if (SetupActivity.isThisImeEnabled(setupWizardActivity, mImmInHandler)) {
                     setupWizardActivity.invokeSetupWizardOfThisIme();
                     return;
                 }
@@ -112,11 +118,12 @@
         setTheme(android.R.style.Theme_Translucent_NoTitleBar);
         super.onCreate(savedInstanceState);
 
+        mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
+        mHandler = new SettingsPoolingHandler(this, mImm);
+
         setContentView(R.layout.setup_wizard);
         mSetupWizard = findViewById(R.id.setup_wizard);
 
-        RichInputMethodManager.init(this);
-
         if (savedInstanceState == null) {
             mStepNumber = determineSetupStepNumberFromLauncher();
         } else {
@@ -143,11 +150,12 @@
                 R.string.setup_step1_title, R.string.setup_step1_instruction,
                 R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1,
                 R.string.setup_step1_action);
+        final SettingsPoolingHandler handler = mHandler;
         step1.setAction(new Runnable() {
             @Override
             public void run() {
                 invokeLanguageAndInputSettings();
-                mHandler.startPollingImeSettings();
+                handler.startPollingImeSettings();
             }
         });
         mSetupStepGroup.addStep(step1);
@@ -265,14 +273,15 @@
 
     void invokeInputMethodPicker() {
         // Invoke input method picker.
-        RichInputMethodManager.getInstance().getInputMethodManager()
-                .showInputMethodPicker();
+        mImm.showInputMethodPicker();
         mNeedsToAdjustStepNumberToSystemState = true;
     }
 
     void invokeSubtypeEnablerOfThisIme() {
-        final InputMethodInfo imi =
-                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+        final InputMethodInfo imi = SetupActivity.getInputMethodInfoOf(getPackageName(), mImm);
+        if (imi == null) {
+            return;
+        }
         final Intent intent = new Intent();
         intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
@@ -293,10 +302,10 @@
 
     private int determineSetupStepNumber() {
         mHandler.cancelPollingImeSettings();
-        if (!SetupActivity.isThisImeEnabled(this)) {
+        if (!SetupActivity.isThisImeEnabled(this, mImm)) {
             return STEP_1;
         }
-        if (!SetupActivity.isThisImeCurrent(this)) {
+        if (!SetupActivity.isThisImeCurrent(this, mImm)) {
             return STEP_2;
         }
         return STEP_3;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
index 13fcaf4..692e739 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java
@@ -25,17 +25,17 @@
 
 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
 import com.android.inputmethod.latin.BinaryDictionary;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.ContactsBinaryDictionary;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.DictionaryCollection;
 import com.android.inputmethod.latin.DictionaryFactory;
-import com.android.inputmethod.latin.LocaleUtils;
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary;
 import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary;
 import com.android.inputmethod.latin.UserBinaryDictionary;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
index 9a1114f..ddda52d 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java
@@ -23,7 +23,7 @@
 import android.view.textservice.SuggestionsInfo;
 import android.view.textservice.TextInfo;
 
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
index 16e9fb7..6719e98 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java
@@ -30,11 +30,11 @@
 import com.android.inputmethod.compat.SuggestionsInfoCompatUtils;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.StringUtils;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
 import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
 
 import java.util.ArrayList;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
index a20e09e..ac8f687 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java
@@ -19,10 +19,10 @@
 import android.util.Log;
 
 import com.android.inputmethod.keyboard.ProximityInfo;
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.WordComposer;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.ArrayList;
 import java.util.Locale;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index 5ce9d8e..999ca77 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -21,7 +21,7 @@
 import android.preference.PreferenceScreen;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 /**
  * Preference screen.
@@ -39,7 +39,7 @@
         addPreferencesFromResource(R.xml.spell_checker_settings);
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
-            preferenceScreen.setTitle(Utils.getAcitivityTitleResId(
+            preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
                     getActivity(), SpellCheckerSettingsActivity.class));
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 09f81d4..e97069d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -24,14 +24,13 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
-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;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.Utils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
@@ -61,7 +60,7 @@
             super();
         }
 
-        public int layout(final SuggestedWords suggestedWords, final int fromPos,
+        public int layout(final SuggestedWords suggestedWords, final int fromIndex,
                 final int maxWidth, final int minWidth, final int maxRow, final Paint paint,
                 final Resources res) {
             clearKeys();
@@ -70,53 +69,54 @@
             final float padding = res.getDimension(R.dimen.more_suggestions_key_horizontal_padding);
 
             int row = 0;
-            int pos = fromPos, rowStartPos = fromPos;
+            int index = fromIndex;
+            int rowStartIndex = fromIndex;
             final int size = Math.min(suggestedWords.size(), SuggestionStripView.MAX_SUGGESTIONS);
-            while (pos < size) {
-                final String word = suggestedWords.getWord(pos);
+            while (index < size) {
+                final String word = suggestedWords.getWord(index);
                 // TODO: Should take care of text x-scaling.
-                mWidths[pos] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
-                final int numColumn = pos - rowStartPos + 1;
+                mWidths[index] = (int)(TypefaceUtils.getLabelWidth(word, paint) + padding);
+                final int numColumn = index - rowStartIndex + 1;
                 final int columnWidth =
                         (maxWidth - mDividerWidth * (numColumn - 1)) / numColumn;
                 if (numColumn > MAX_COLUMNS_IN_ROW
-                        || !fitInWidth(rowStartPos, pos + 1, columnWidth)) {
+                        || !fitInWidth(rowStartIndex, index + 1, columnWidth)) {
                     if ((row + 1) >= maxRow) {
                         break;
                     }
-                    mNumColumnsInRow[row] = pos - rowStartPos;
-                    rowStartPos = pos;
+                    mNumColumnsInRow[row] = index - rowStartIndex;
+                    rowStartIndex = index;
                     row++;
                 }
-                mColumnOrders[pos] = pos - rowStartPos;
-                mRowNumbers[pos] = row;
-                pos++;
+                mColumnOrders[index] = index - rowStartIndex;
+                mRowNumbers[index] = row;
+                index++;
             }
-            mNumColumnsInRow[row] = pos - rowStartPos;
+            mNumColumnsInRow[row] = index - rowStartIndex;
             mNumRows = row + 1;
             mBaseWidth = mOccupiedWidth = Math.max(
-                    minWidth, calcurateMaxRowWidth(fromPos, pos));
+                    minWidth, calcurateMaxRowWidth(fromIndex, index));
             mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap;
-            return pos - fromPos;
+            return index - fromIndex;
         }
 
-        private boolean fitInWidth(final int startPos, final int endPos, final int width) {
-            for (int pos = startPos; pos < endPos; pos++) {
-                if (mWidths[pos] > width)
+        private boolean fitInWidth(final int startIndex, final int endIndex, final int width) {
+            for (int index = startIndex; index < endIndex; index++) {
+                if (mWidths[index] > width)
                     return false;
             }
             return true;
         }
 
-        private int calcurateMaxRowWidth(final int startPos, final int endPos) {
+        private int calcurateMaxRowWidth(final int startIndex, final int endIndex) {
             int maxRowWidth = 0;
-            int pos = startPos;
+            int index = startIndex;
             for (int row = 0; row < mNumRows; row++) {
                 final int numColumnInRow = mNumColumnsInRow[row];
                 int maxKeyWidth = 0;
-                while (pos < endPos && mRowNumbers[pos] == row) {
-                    maxKeyWidth = Math.max(maxKeyWidth, mWidths[pos]);
-                    pos++;
+                while (index < endIndex && mRowNumbers[index] == row) {
+                    maxKeyWidth = Math.max(maxKeyWidth, mWidths[index]);
+                    index++;
                 }
                 maxRowWidth = Math.max(maxRowWidth,
                         maxKeyWidth * numColumnInRow + mDividerWidth * (numColumnInRow - 1));
@@ -130,40 +130,40 @@
             { 2, 0, 1},
         };
 
-        public int getNumColumnInRow(final int pos) {
-            return mNumColumnsInRow[mRowNumbers[pos]];
+        public int getNumColumnInRow(final int index) {
+            return mNumColumnsInRow[mRowNumbers[index]];
         }
 
-        public int getColumnNumber(final int pos) {
-            final int columnOrder = mColumnOrders[pos];
-            final int numColumn = getNumColumnInRow(pos);
+        public int getColumnNumber(final int index) {
+            final int columnOrder = mColumnOrders[index];
+            final int numColumn = getNumColumnInRow(index);
             return COLUMN_ORDER_TO_NUMBER[numColumn - 1][columnOrder];
         }
 
-        public int getX(final int pos) {
-            final int columnNumber = getColumnNumber(pos);
-            return columnNumber * (getWidth(pos) + mDividerWidth);
+        public int getX(final int index) {
+            final int columnNumber = getColumnNumber(index);
+            return columnNumber * (getWidth(index) + mDividerWidth);
         }
 
-        public int getY(final int pos) {
-            final int row = mRowNumbers[pos];
+        public int getY(final int index) {
+            final int row = mRowNumbers[index];
             return (mNumRows -1 - row) * mDefaultRowHeight + mTopPadding;
         }
 
-        public int getWidth(final int pos) {
-            final int numColumnInRow = getNumColumnInRow(pos);
+        public int getWidth(final int index) {
+            final int numColumnInRow = getNumColumnInRow(index);
             return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow;
         }
 
-        public void markAsEdgeKey(final Key key, final int pos) {
-            final int row = mRowNumbers[pos];
+        public void markAsEdgeKey(final Key key, final int index) {
+            final int row = mRowNumbers[index];
             if (row == 0)
                 key.markAsBottomEdge(this);
             if (row == mNumRows - 1)
                 key.markAsTopEdge(this);
 
             final int numColumnInRow = mNumColumnsInRow[row];
-            final int column = getColumnNumber(pos);
+            final int column = getColumnNumber(index);
             if (column == 0)
                 key.markAsLeftEdge(this);
             if (column == numColumnInRow - 1)
@@ -174,15 +174,15 @@
     public static final class Builder extends KeyboardBuilder<MoreSuggestionsParam> {
         private final MoreSuggestionsView mPaneView;
         private SuggestedWords mSuggestedWords;
-        private int mFromPos;
-        private int mToPos;
+        private int mFromIndex;
+        private int mToIndex;
 
         public Builder(final Context context, final MoreSuggestionsView paneView) {
             super(context, new MoreSuggestionsParam());
             mPaneView = paneView;
         }
 
-        public Builder layout(final SuggestedWords suggestedWords, final int fromPos,
+        public Builder layout(final SuggestedWords suggestedWords, final int fromIndex,
                 final int maxWidth, final int minWidth, final int maxRow,
                 final Keyboard parentKeyboard) {
             final int xmlId = R.xml.kbd_suggestions_pane_template;
@@ -190,10 +190,10 @@
             mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
 
             mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight);
-            final int count = mParams.layout(suggestedWords, fromPos, maxWidth, minWidth, maxRow,
+            final int count = mParams.layout(suggestedWords, fromIndex, maxWidth, minWidth, maxRow,
                     mPaneView.newLabelPaint(null /* key */), mResources);
-            mFromPos = fromPos;
-            mToPos = fromPos + count;
+            mFromIndex = fromIndex;
+            mToIndex = fromIndex + count;
             mSuggestedWords = suggestedWords;
             return this;
         }
@@ -201,20 +201,20 @@
         @Override
         public MoreSuggestions build() {
             final MoreSuggestionsParam params = mParams;
-            for (int pos = mFromPos; pos < mToPos; pos++) {
-                final int x = params.getX(pos);
-                final int y = params.getY(pos);
-                final int width = params.getWidth(pos);
-                final String word = mSuggestedWords.getWord(pos);
-                final String info = Utils.getDebugInfo(mSuggestedWords, pos);
-                final int index = pos + SUGGESTION_CODE_BASE;
+            for (int index = mFromIndex; index < mToIndex; index++) {
+                final int x = params.getX(index);
+                final int y = params.getY(index);
+                final int width = params.getWidth(index);
+                final String word = mSuggestedWords.getWord(index);
+                final String info = mSuggestedWords.getDebugString(index);
+                final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
-                        params, word, info, KeyboardIconsSet.ICON_UNDEFINED, index, null, x, y,
-                        width, params.mDefaultRowHeight, 0);
-                params.markAsEdgeKey(key, pos);
+                        params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
+                        null, x, y, width, params.mDefaultRowHeight, 0);
+                params.markAsEdgeKey(key, index);
                 params.onAddKey(key);
-                final int columnNumber = params.getColumnNumber(pos);
-                final int numColumnInRow = params.getNumColumnInRow(pos);
+                final int columnNumber = params.getColumnNumber(index);
+                final int numColumnInRow = params.getNumColumnInRow(index);
                 if (columnNumber < numColumnInRow - 1) {
                     final Divider divider = new Divider(params, params.mDivider, x + width, y,
                             params.mDividerWidth, params.mDefaultRowHeight);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
new file mode 100644
index 0000000..1dd04fc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -0,0 +1,600 @@
+/*
+ * 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.suggestions;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Align;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.text.style.UnderlineSpan;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.utils.AutoCorrectionUtils;
+import com.android.inputmethod.latin.utils.ResourceUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
+
+import java.util.ArrayList;
+
+final class SuggestionStripLayoutHelper {
+    private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
+    private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
+    private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
+    private static final int PUNCTUATIONS_IN_STRIP = 5;
+    private static final float MIN_TEXT_XSCALE = 0.70f;
+
+    public final int mPadding;
+    public final int mDividerWidth;
+    public final int mSuggestionsStripHeight;
+    public final int mSuggestionsCountInStrip;
+    public final int mMoreSuggestionsRowHeight;
+    private int mMaxMoreSuggestionsRow;
+    public final float mMinMoreSuggestionsWidth;
+    public final int mMoreSuggestionsBottomGap;
+    public boolean mMoreSuggestionsAvailable;
+
+    // The index of these {@link ArrayList} is the position in the suggestion strip. The indices
+    // increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+    // The position of the most important suggestion is in {@link #mCenterPositionInStrip}
+    private final ArrayList<TextView> mWordViews;
+    private final ArrayList<View> mDividerViews;
+    private final ArrayList<TextView> mDebugInfoViews;
+
+    private final int mColorValidTypedWord;
+    private final int mColorTypedWord;
+    private final int mColorAutoCorrect;
+    private final int mColorSuggested;
+    private final float mAlphaObsoleted;
+    private final float mCenterSuggestionWeight;
+    private final int mCenterPositionInStrip;
+    private final int mTypedWordPositionWhenAutocorrect;
+    private final Drawable mMoreSuggestionsHint;
+    private static final String MORE_SUGGESTIONS_HINT = "\u2026";
+    private static final String LEFTWARDS_ARROW = "\u2190";
+
+    private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
+    private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
+
+    private final int mSuggestionStripOption;
+    // These constants are the flag values of
+    // {@link R.styleable#SuggestionStripView_suggestionStripOption} attribute.
+    private static final int AUTO_CORRECT_BOLD = 0x01;
+    private static final int AUTO_CORRECT_UNDERLINE = 0x02;
+    private static final int VALID_TYPED_WORD_BOLD = 0x04;
+
+    private final TextView mWordToSaveView;
+    private final TextView mLeftwardsArrowView;
+    private final TextView mHintToSaveView;
+
+    public SuggestionStripLayoutHelper(final Context context, final AttributeSet attrs,
+            final int defStyle, final ArrayList<TextView> wordViews,
+            final ArrayList<View> dividerViews, final ArrayList<TextView> debugInfoViews) {
+        mWordViews = wordViews;
+        mDividerViews = dividerViews;
+        mDebugInfoViews = debugInfoViews;
+
+        final TextView wordView = wordViews.get(0);
+        final View dividerView = dividerViews.get(0);
+        mPadding = wordView.getCompoundPaddingLeft() + wordView.getCompoundPaddingRight();
+        dividerView.measure(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mDividerWidth = dividerView.getMeasuredWidth();
+
+        final Resources res = wordView.getResources();
+        mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
+
+        final TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
+        mSuggestionStripOption = a.getInt(
+                R.styleable.SuggestionStripView_suggestionStripOption, 0);
+        final float alphaValidTypedWord = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
+        final float alphaTypedWord = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
+        final float alphaAutoCorrect = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
+        final float alphaSuggested = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+        mAlphaObsoleted = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
+        mColorValidTypedWord = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
+        mColorTypedWord = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
+        mColorAutoCorrect = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
+        mColorSuggested = applyAlpha(a.getColor(
+                R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
+        mSuggestionsCountInStrip = a.getInt(
+                R.styleable.SuggestionStripView_suggestionsCountInStrip,
+                DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
+        mCenterSuggestionWeight = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_centerSuggestionPercentile,
+                DEFAULT_CENTER_SUGGESTION_PERCENTILE);
+        mMaxMoreSuggestionsRow = a.getInt(
+                R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
+                DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
+        mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
+                R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
+        a.recycle();
+
+        mMoreSuggestionsHint = getMoreSuggestionsHint(res,
+                res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
+        mCenterPositionInStrip = mSuggestionsCountInStrip / 2;
+        // Assuming there are at least three suggestions. Also, note that the suggestions are
+        // laid out according to script direction, so this is left of the center for LTR scripts
+        // and right of the center for RTL scripts.
+        mTypedWordPositionWhenAutocorrect = mCenterPositionInStrip - 1;
+        mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
+                R.dimen.more_suggestions_bottom_gap);
+        mMoreSuggestionsRowHeight = res.getDimensionPixelSize(R.dimen.more_suggestions_row_height);
+
+        final LayoutInflater inflater = LayoutInflater.from(context);
+        mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
+        mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+        mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
+    }
+
+    public int getMaxMoreSuggestionsRow() {
+        return mMaxMoreSuggestionsRow;
+    }
+
+    private int getMoreSuggestionsHeight() {
+        return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
+    }
+
+    public int setMoreSuggestionsHeight(final int remainingHeight) {
+        final int currentHeight = getMoreSuggestionsHeight();
+        if (currentHeight <= remainingHeight) {
+            return currentHeight;
+        }
+
+        mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
+                / mMoreSuggestionsRowHeight;
+        final int newHeight = getMoreSuggestionsHeight();
+        return newHeight;
+    }
+
+    private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
+            final int color) {
+        final Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setTextAlign(Align.CENTER);
+        paint.setTextSize(textSize);
+        paint.setColor(color);
+        final Rect bounds = new Rect();
+        paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
+        final int width = Math.round(bounds.width() + 0.5f);
+        final int height = Math.round(bounds.height() + 0.5f);
+        final Bitmap buffer = Bitmap.createBitmap(width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(buffer);
+        canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
+        return new BitmapDrawable(res, buffer);
+    }
+
+    private CharSequence getStyledSuggestedWord(final SuggestedWords suggestedWords,
+            final int indexInSuggestedWords) {
+        if (indexInSuggestedWords >= suggestedWords.size()) {
+            return null;
+        }
+        final String word = suggestedWords.getWord(indexInSuggestedWords);
+        final boolean isAutoCorrect = indexInSuggestedWords == 1
+                && suggestedWords.willAutoCorrect();
+        final boolean isTypedWordValid = indexInSuggestedWords == 0
+                && suggestedWords.mTypedWordValid;
+        if (!isAutoCorrect && !isTypedWordValid) {
+            return word;
+        }
+
+        final int len = word.length();
+        final Spannable spannedWord = new SpannableString(word);
+        final int option = mSuggestionStripOption;
+        if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
+                || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
+            spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
+            spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        }
+        return spannedWord;
+    }
+
+    private int getPositionInSuggestionStrip(final int indexInSuggestedWords,
+            final SuggestedWords suggestedWords) {
+        final int indexToDisplayMostImportantSuggestion;
+        final int indexToDisplaySecondMostImportantSuggestion;
+        if (suggestedWords.willAutoCorrect()) {
+            indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+            indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+        } else {
+            indexToDisplayMostImportantSuggestion = SuggestedWords.INDEX_OF_TYPED_WORD;
+            indexToDisplaySecondMostImportantSuggestion = SuggestedWords.INDEX_OF_AUTO_CORRECTION;
+        }
+        if (indexInSuggestedWords == indexToDisplayMostImportantSuggestion) {
+            return mCenterPositionInStrip;
+        }
+        if (indexInSuggestedWords == indexToDisplaySecondMostImportantSuggestion) {
+            return mTypedWordPositionWhenAutocorrect;
+        }
+        // If neither of those, the order in the suggestion strip is the same as in SuggestedWords.
+        return indexInSuggestedWords;
+    }
+
+    private int getSuggestionTextColor(final int indexInSuggestedWords,
+            final SuggestedWords suggestedWords) {
+        final int positionInStrip =
+                getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+        // TODO: Need to revisit this logic with bigram suggestions
+        final boolean isSuggested = (indexInSuggestedWords != SuggestedWords.INDEX_OF_TYPED_WORD);
+
+        final int color;
+        if (positionInStrip == mCenterPositionInStrip && suggestedWords.willAutoCorrect()) {
+            color = mColorAutoCorrect;
+        } else if (positionInStrip == mCenterPositionInStrip && suggestedWords.mTypedWordValid) {
+            color = mColorValidTypedWord;
+        } else if (isSuggested) {
+            color = mColorSuggested;
+        } else {
+            color = mColorTypedWord;
+        }
+        if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
+            // If we auto-correct, then the autocorrection is in slot 0 and the typed word
+            // is in slot 1.
+            if (positionInStrip == mCenterPositionInStrip
+                    && AutoCorrectionUtils.shouldBlockAutoCorrectionBySafetyNet(
+                            suggestedWords.getWord(SuggestedWords.INDEX_OF_AUTO_CORRECTION),
+                            suggestedWords.getWord(SuggestedWords.INDEX_OF_TYPED_WORD))) {
+                return 0xFFFF0000;
+            }
+        }
+
+        if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
+            return applyAlpha(color, mAlphaObsoleted);
+        }
+        return color;
+    }
+
+    private static int applyAlpha(final int color, final float alpha) {
+        final int newAlpha = (int)(Color.alpha(color) * alpha);
+        return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
+    }
+
+    private static void addDivider(final ViewGroup stripView, final View dividerView) {
+        stripView.addView(dividerView);
+        final LinearLayout.LayoutParams params =
+                (LinearLayout.LayoutParams)dividerView.getLayoutParams();
+        params.gravity = Gravity.CENTER;
+    }
+
+    public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
+            final ViewGroup placerView) {
+        if (suggestedWords.mIsPunctuationSuggestions) {
+            layoutPunctuationSuggestions(suggestedWords, stripView);
+            return;
+        }
+
+        final int countInStrip = mSuggestionsCountInStrip;
+        setupWordViewsTextAndColor(suggestedWords, countInStrip);
+        final TextView centerWordView = mWordViews.get(mCenterPositionInStrip);
+        final int stripWidth = placerView.getWidth();
+        final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth);
+        if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint())
+                < MIN_TEXT_XSCALE) {
+            // Layout only the most relevant suggested word at the center of the suggestion strip
+            // by consolidating all slots in the strip.
+            mMoreSuggestionsAvailable = (suggestedWords.size() > 1);
+            layoutWord(mCenterPositionInStrip, stripWidth);
+            stripView.addView(centerWordView);
+            setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT);
+            if (SuggestionStripView.DBG) {
+                layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth);
+            }
+            return;
+        }
+
+        mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+        int x = 0;
+        for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+            if (positionInStrip != 0) {
+                final View divider = mDividerViews.get(positionInStrip);
+                // Add divider if this isn't the left most suggestion in suggestions strip.
+                addDivider(stripView, divider);
+                x += divider.getMeasuredWidth();
+            }
+
+            final int width = getSuggestionWidth(positionInStrip, stripWidth);
+            final TextView wordView = layoutWord(positionInStrip, width);
+            stripView.addView(wordView);
+            setLayoutWeight(wordView, getSuggestionWeight(positionInStrip),
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            x += wordView.getMeasuredWidth();
+
+            if (SuggestionStripView.DBG) {
+                layoutDebugInfo(positionInStrip, placerView, x);
+            }
+        }
+    }
+
+    /**
+     * Format appropriately the suggested word in {@link #mWordViews} specified by
+     * <code>positionInStrip</code>. When the suggested word doesn't exist, the corresponding
+     * {@link TextView} will be disabled and never respond to user interaction. The suggested word
+     * may be shrunk or ellipsized to fit in the specified width.
+     *
+     * The <code>positionInStrip</code> argument is the index in the suggestion strip. The indices
+     * increase towards the right for LTR scripts and the left for RTL scripts, starting with 0.
+     * The position of the most important suggestion is in {@link #mCenterPositionInStrip}. This
+     * usually doesn't match the index in <code>suggedtedWords</code> -- see
+     * {@link #getPositionInSuggestionStrip(int,SuggestedWords)}.
+     *
+     * @param positionInStrip the position in the suggestion strip.
+     * @param width the maximum width for layout in pixels.
+     * @return the {@link TextView} containing the suggested word appropriately formatted.
+     */
+    private TextView layoutWord(final int positionInStrip, final int width) {
+        final TextView wordView = mWordViews.get(positionInStrip);
+        final CharSequence word = wordView.getText();
+        if (positionInStrip == mCenterPositionInStrip && mMoreSuggestionsAvailable) {
+            // TODO: This "more suggestions hint" should have a nicely designed icon.
+            wordView.setCompoundDrawablesWithIntrinsicBounds(
+                    null, null, null, mMoreSuggestionsHint);
+            // HACK: Align with other TextViews that have no compound drawables.
+            wordView.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
+        } else {
+            wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
+        }
+
+        // Disable this suggestion if the suggestion is null or empty.
+        wordView.setEnabled(!TextUtils.isEmpty(word));
+        final CharSequence text = getEllipsizedText(word, width, wordView.getPaint());
+        final float scaleX = wordView.getTextScaleX();
+        wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
+        wordView.setTextScaleX(scaleX);
+        return wordView;
+    }
+
+    private void layoutDebugInfo(final int positionInStrip, final ViewGroup placerView,
+            final int x) {
+        final TextView debugInfoView = mDebugInfoViews.get(positionInStrip);
+        final CharSequence debugInfo = debugInfoView.getText();
+        if (debugInfo == null) {
+            return;
+        }
+        placerView.addView(debugInfoView);
+        debugInfoView.measure(
+                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        final int infoWidth = debugInfoView.getMeasuredWidth();
+        final int y = debugInfoView.getMeasuredHeight();
+        ViewLayoutUtils.placeViewAt(
+                debugInfoView, x - infoWidth, y, infoWidth, debugInfoView.getMeasuredHeight());
+    }
+
+    private int getSuggestionWidth(final int positionInStrip, final int maxWidth) {
+        final int paddings = mPadding * mSuggestionsCountInStrip;
+        final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
+        final int availableWidth = maxWidth - paddings - dividers;
+        return (int)(availableWidth * getSuggestionWeight(positionInStrip));
+    }
+
+    private float getSuggestionWeight(final int positionInStrip) {
+        if (positionInStrip == mCenterPositionInStrip) {
+            return mCenterSuggestionWeight;
+        }
+        // TODO: Revisit this for cases of 5 or more suggestions
+        return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
+    }
+
+    private void setupWordViewsTextAndColor(final SuggestedWords suggestedWords,
+            final int countInStrip) {
+        // Clear all suggestions first
+        for (int positionInStrip = 0; positionInStrip < countInStrip; ++positionInStrip) {
+            mWordViews.get(positionInStrip).setText(null);
+            // Make this inactive for touches in {@link #layoutWord(int,int)}.
+            if (SuggestionStripView.DBG) {
+                mDebugInfoViews.get(positionInStrip).setText(null);
+            }
+        }
+        final int count = Math.min(suggestedWords.size(), countInStrip);
+        for (int indexInSuggestedWords = 0; indexInSuggestedWords < count;
+                indexInSuggestedWords++) {
+            final int positionInStrip =
+                    getPositionInSuggestionStrip(indexInSuggestedWords, suggestedWords);
+            final TextView wordView = mWordViews.get(positionInStrip);
+            // {@link TextView#getTag()} is used to get the index in suggestedWords at
+            // {@link SuggestionStripView#onClick(View)}.
+            wordView.setTag(indexInSuggestedWords);
+            wordView.setText(getStyledSuggestedWord(suggestedWords, indexInSuggestedWords));
+            wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
+            if (SuggestionStripView.DBG) {
+                mDebugInfoViews.get(positionInStrip).setText(
+                        suggestedWords.getDebugString(indexInSuggestedWords));
+            }
+        }
+    }
+
+    private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
+            final ViewGroup stripView) {
+        final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
+        for (int positionInStrip = 0; positionInStrip < countInStrip; positionInStrip++) {
+            if (positionInStrip != 0) {
+                // Add divider if this isn't the left most suggestion in suggestions strip.
+                addDivider(stripView, mDividerViews.get(positionInStrip));
+            }
+
+            final TextView wordView = mWordViews.get(positionInStrip);
+            wordView.setEnabled(true);
+            wordView.setTextColor(mColorAutoCorrect);
+            // {@link TextView#getTag()} is used to get the index in suggestedWords at
+            // {@link SuggestionStripView#onClick(View)}.
+            wordView.setTag(positionInStrip);
+            wordView.setText(suggestedWords.getWord(positionInStrip));
+            wordView.setTextScaleX(1.0f);
+            wordView.setCompoundDrawables(null, null, null, null);
+            stripView.addView(wordView);
+            setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight);
+        }
+        mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
+    }
+
+    public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
+            final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
+        final int width = stripWidth - mDividerWidth - mPadding * 2;
+
+        final TextView wordView = mWordToSaveView;
+        wordView.setTextColor(mColorTypedWord);
+        final int wordWidth = (int)(width * mCenterSuggestionWeight);
+        final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
+        final float wordScaleX = wordView.getTextScaleX();
+        // {@link TextView#setTag()} is used to hold the word to be added to dictionary. The word
+        // will be extracted at {@link #getAddToDictionaryWord()}.
+        wordView.setTag(word);
+        wordView.setText(text);
+        wordView.setTextScaleX(wordScaleX);
+        stripView.addView(wordView);
+        setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+        stripView.addView(mDividerViews.get(0));
+
+        final TextView leftArrowView = mLeftwardsArrowView;
+        leftArrowView.setTextColor(mColorAutoCorrect);
+        leftArrowView.setText(LEFTWARDS_ARROW);
+        stripView.addView(leftArrowView);
+
+        final TextView hintView = mHintToSaveView;
+        hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+        hintView.setTextColor(mColorAutoCorrect);
+        final int hintWidth = width - wordWidth - leftArrowView.getWidth();
+        final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
+        hintView.setText(hintText);
+        hintView.setTextScaleX(hintScaleX);
+        stripView.addView(hintView);
+        setLayoutWeight(
+                hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
+
+        wordView.setOnClickListener(listener);
+        leftArrowView.setOnClickListener(listener);
+        hintView.setOnClickListener(listener);
+    }
+
+    public String getAddToDictionaryWord() {
+        // String tag is set at
+        // {@link #layoutAddToDictionaryHint(String,ViewGroup,int,CharSequence,OnClickListener}.
+        return (String)mWordToSaveView.getTag();
+    }
+
+    public boolean isAddToDictionaryShowing(final View v) {
+        return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
+    }
+
+    private static void setLayoutWeight(final View v, final float weight, final int height) {
+        final ViewGroup.LayoutParams lp = v.getLayoutParams();
+        if (lp instanceof LinearLayout.LayoutParams) {
+            final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
+            llp.weight = weight;
+            llp.width = 0;
+            llp.height = height;
+        }
+    }
+
+    private static float getTextScaleX(final CharSequence text, final int maxWidth,
+            final TextPaint paint) {
+        paint.setTextScaleX(1.0f);
+        final int width = getTextWidth(text, paint);
+        if (width <= maxWidth) {
+            return 1.0f;
+        }
+        return maxWidth / (float)width;
+    }
+
+    private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
+            final TextPaint paint) {
+        if (text == null) {
+            return null;
+        }
+        final float scaleX = getTextScaleX(text, maxWidth, paint);
+        if (scaleX >= MIN_TEXT_XSCALE) {
+            paint.setTextScaleX(scaleX);
+            return text;
+        }
+
+        // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
+        // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
+        final CharSequence ellipsized = TextUtils.ellipsize(
+                text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
+        paint.setTextScaleX(MIN_TEXT_XSCALE);
+        return ellipsized;
+    }
+
+    private static int getTextWidth(final CharSequence text, final TextPaint paint) {
+        if (TextUtils.isEmpty(text)) {
+            return 0;
+        }
+        final Typeface savedTypeface = paint.getTypeface();
+        paint.setTypeface(getTextTypeface(text));
+        final int len = text.length();
+        final float[] widths = new float[len];
+        final int count = paint.getTextWidths(text, 0, len, widths);
+        int width = 0;
+        for (int i = 0; i < count; i++) {
+            width += Math.round(widths[i] + 0.5f);
+        }
+        paint.setTypeface(savedTypeface);
+        return width;
+    }
+
+    private static Typeface getTextTypeface(final CharSequence text) {
+        if (!(text instanceof SpannableString)) {
+            return Typeface.DEFAULT;
+        }
+
+        final SpannableString ss = (SpannableString)text;
+        final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
+        if (styles.length == 0) {
+            return Typeface.DEFAULT;
+        }
+
+        if (styles[0].getStyle() == Typeface.BOLD) {
+            return Typeface.DEFAULT_BOLD;
+        }
+        // TODO: BOLD_ITALIC, ITALIC case?
+        return Typeface.DEFAULT;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index ad350a0..497a791 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -18,34 +18,14 @@
 
 import android.content.Context;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Align;
-import android.graphics.Rect;
-import android.graphics.Typeface;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.Spanned;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.text.style.CharacterStyle;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
 import android.util.AttributeSet;
 import android.view.GestureDetector;
-import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
@@ -53,18 +33,15 @@
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
-import com.android.inputmethod.keyboard.ViewLayoutUtils;
-import com.android.inputmethod.latin.AutoCorrection;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.LatinImeLogger;
 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.latin.suggestions.MoreSuggestions.MoreSuggestionsListener;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.ArrayList;
@@ -88,477 +65,14 @@
     private final MoreSuggestionsView mMoreSuggestionsView;
     private final MoreSuggestions.Builder mMoreSuggestionsBuilder;
 
-    private final ArrayList<TextView> mWords = CollectionUtils.newArrayList();
-    private final ArrayList<TextView> mInfos = CollectionUtils.newArrayList();
-    private final ArrayList<View> mDividers = CollectionUtils.newArrayList();
+    private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList();
+    private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList();
+    private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList();
 
     Listener mListener;
     private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
 
-    private final SuggestionStripViewParams mParams;
-    private static final float MIN_TEXT_XSCALE = 0.70f;
-
-    private static final class SuggestionStripViewParams {
-        private static final int DEFAULT_SUGGESTIONS_COUNT_IN_STRIP = 3;
-        private static final float DEFAULT_CENTER_SUGGESTION_PERCENTILE = 0.40f;
-        private static final int DEFAULT_MAX_MORE_SUGGESTIONS_ROW = 2;
-        private static final int PUNCTUATIONS_IN_STRIP = 5;
-
-        public final int mPadding;
-        public final int mDividerWidth;
-        public final int mSuggestionsStripHeight;
-        public final int mSuggestionsCountInStrip;
-        public final int mMoreSuggestionsRowHeight;
-        private int mMaxMoreSuggestionsRow;
-        public final float mMinMoreSuggestionsWidth;
-        public final int mMoreSuggestionsBottomGap;
-
-        private final ArrayList<TextView> mWords;
-        private final ArrayList<View> mDividers;
-        private final ArrayList<TextView> mInfos;
-
-        private final int mColorValidTypedWord;
-        private final int mColorTypedWord;
-        private final int mColorAutoCorrect;
-        private final int mColorSuggested;
-        private final float mAlphaObsoleted;
-        private final float mCenterSuggestionWeight;
-        private final int mCenterSuggestionIndex;
-        private final Drawable mMoreSuggestionsHint;
-        private static final String MORE_SUGGESTIONS_HINT = "\u2026";
-        private static final String LEFTWARDS_ARROW = "\u2190";
-
-        private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD);
-        private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
-        private static final int AUTO_CORRECT_BOLD = 0x01;
-        private static final int AUTO_CORRECT_UNDERLINE = 0x02;
-        private static final int VALID_TYPED_WORD_BOLD = 0x04;
-
-        private final int mSuggestionStripOption;
-
-        private final ArrayList<CharSequence> mTexts = CollectionUtils.newArrayList();
-
-        public boolean mMoreSuggestionsAvailable;
-
-        private final TextView mWordToSaveView;
-        private final TextView mLeftwardsArrowView;
-        private final TextView mHintToSaveView;
-
-        public SuggestionStripViewParams(final Context context, final AttributeSet attrs,
-                final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers,
-                final ArrayList<TextView> infos) {
-            mWords = words;
-            mDividers = dividers;
-            mInfos = infos;
-
-            final TextView word = words.get(0);
-            final View divider = dividers.get(0);
-            mPadding = word.getCompoundPaddingLeft() + word.getCompoundPaddingRight();
-            divider.measure(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-            mDividerWidth = divider.getMeasuredWidth();
-
-            final Resources res = word.getResources();
-            mSuggestionsStripHeight = res.getDimensionPixelSize(R.dimen.suggestions_strip_height);
-
-            final TypedArray a = context.obtainStyledAttributes(attrs,
-                    R.styleable.SuggestionStripView, defStyle, R.style.SuggestionStripViewStyle);
-            mSuggestionStripOption = a.getInt(
-                    R.styleable.SuggestionStripView_suggestionStripOption, 0);
-            final float alphaValidTypedWord = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaValidTypedWord, 1.0f);
-            final float alphaTypedWord = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaTypedWord, 1.0f);
-            final float alphaAutoCorrect = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaAutoCorrect, 1.0f);
-            final float alphaSuggested = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-            mAlphaObsoleted = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_alphaSuggested, 1.0f);
-            mColorValidTypedWord = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorValidTypedWord, 0), alphaValidTypedWord);
-            mColorTypedWord = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorTypedWord, 0), alphaTypedWord);
-            mColorAutoCorrect = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorAutoCorrect, 0), alphaAutoCorrect);
-            mColorSuggested = applyAlpha(a.getColor(
-                    R.styleable.SuggestionStripView_colorSuggested, 0), alphaSuggested);
-            mSuggestionsCountInStrip = a.getInt(
-                    R.styleable.SuggestionStripView_suggestionsCountInStrip,
-                    DEFAULT_SUGGESTIONS_COUNT_IN_STRIP);
-            mCenterSuggestionWeight = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_centerSuggestionPercentile,
-                    DEFAULT_CENTER_SUGGESTION_PERCENTILE);
-            mMaxMoreSuggestionsRow = a.getInt(
-                    R.styleable.SuggestionStripView_maxMoreSuggestionsRow,
-                    DEFAULT_MAX_MORE_SUGGESTIONS_ROW);
-            mMinMoreSuggestionsWidth = ResourceUtils.getFraction(a,
-                    R.styleable.SuggestionStripView_minMoreSuggestionsWidth, 1.0f);
-            a.recycle();
-
-            mMoreSuggestionsHint = getMoreSuggestionsHint(res,
-                    res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect);
-            mCenterSuggestionIndex = mSuggestionsCountInStrip / 2;
-            mMoreSuggestionsBottomGap = res.getDimensionPixelOffset(
-                    R.dimen.more_suggestions_bottom_gap);
-            mMoreSuggestionsRowHeight = res.getDimensionPixelSize(
-                    R.dimen.more_suggestions_row_height);
-
-            final LayoutInflater inflater = LayoutInflater.from(context);
-            mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            mLeftwardsArrowView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
-            mHintToSaveView = (TextView)inflater.inflate(R.layout.hint_add_to_dictionary, null);
-        }
-
-        public int getMaxMoreSuggestionsRow() {
-            return mMaxMoreSuggestionsRow;
-        }
-
-        private int getMoreSuggestionsHeight() {
-            return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap;
-        }
-
-        public int setMoreSuggestionsHeight(final int remainingHeight) {
-            final int currentHeight = getMoreSuggestionsHeight();
-            if (currentHeight <= remainingHeight) {
-                return currentHeight;
-            }
-
-            mMaxMoreSuggestionsRow = (remainingHeight - mMoreSuggestionsBottomGap)
-                    / mMoreSuggestionsRowHeight;
-            final int newHeight = getMoreSuggestionsHeight();
-            return newHeight;
-        }
-
-        private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize,
-                final int color) {
-            final Paint paint = new Paint();
-            paint.setAntiAlias(true);
-            paint.setTextAlign(Align.CENTER);
-            paint.setTextSize(textSize);
-            paint.setColor(color);
-            final Rect bounds = new Rect();
-            paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds);
-            final int width = Math.round(bounds.width() + 0.5f);
-            final int height = Math.round(bounds.height() + 0.5f);
-            final Bitmap buffer = Bitmap.createBitmap(
-                    width, (height * 3 / 2), Bitmap.Config.ARGB_8888);
-            final Canvas canvas = new Canvas(buffer);
-            canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint);
-            return new BitmapDrawable(res, buffer);
-        }
-
-        private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords,
-                final int pos) {
-            final String word = suggestedWords.getWord(pos);
-            final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect();
-            final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid;
-            if (!isAutoCorrect && !isTypedWordValid)
-                return word;
-
-            final int len = word.length();
-            final Spannable spannedWord = new SpannableString(word);
-            final int option = mSuggestionStripOption;
-            if ((isAutoCorrect && (option & AUTO_CORRECT_BOLD) != 0)
-                    || (isTypedWordValid && (option & VALID_TYPED_WORD_BOLD) != 0)) {
-                spannedWord.setSpan(BOLD_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-            }
-            if (isAutoCorrect && (option & AUTO_CORRECT_UNDERLINE) != 0) {
-                spannedWord.setSpan(UNDERLINE_SPAN, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
-            }
-            return spannedWord;
-        }
-
-        private int getWordPosition(final int index, final SuggestedWords suggestedWords) {
-            // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more
-            // suggestions.
-            final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0;
-            if (index == mCenterSuggestionIndex) {
-                return centerPos;
-            } else if (index == centerPos) {
-                return mCenterSuggestionIndex;
-            } else {
-                return index;
-            }
-        }
-
-        private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords,
-                final int pos) {
-            // TODO: Need to revisit this logic with bigram suggestions
-            final boolean isSuggested = (pos != 0);
-
-            final int color;
-            if (index == mCenterSuggestionIndex && suggestedWords.willAutoCorrect()) {
-                color = mColorAutoCorrect;
-            } else if (index == mCenterSuggestionIndex && suggestedWords.mTypedWordValid) {
-                color = mColorValidTypedWord;
-            } else if (isSuggested) {
-                color = mColorSuggested;
-            } else {
-                color = mColorTypedWord;
-            }
-            if (LatinImeLogger.sDBG && suggestedWords.size() > 1) {
-                // If we auto-correct, then the autocorrection is in slot 0 and the typed word
-                // is in slot 1.
-                if (index == mCenterSuggestionIndex
-                        && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet(
-                                suggestedWords.getWord(1), suggestedWords.getWord(0))) {
-                    return 0xFFFF0000;
-                }
-            }
-
-            if (suggestedWords.mIsObsoleteSuggestions && isSuggested) {
-                return applyAlpha(color, mAlphaObsoleted);
-            } else {
-                return color;
-            }
-        }
-
-        private static int applyAlpha(final int color, final float alpha) {
-            final int newAlpha = (int)(Color.alpha(color) * alpha);
-            return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color));
-        }
-
-        private static void addDivider(final ViewGroup stripView, final View divider) {
-            stripView.addView(divider);
-            final LinearLayout.LayoutParams params =
-                    (LinearLayout.LayoutParams)divider.getLayoutParams();
-            params.gravity = Gravity.CENTER;
-        }
-
-        public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView,
-                final ViewGroup placer, final int stripWidth) {
-            if (suggestedWords.mIsPunctuationSuggestions) {
-                layoutPunctuationSuggestions(suggestedWords, stripView);
-                return;
-            }
-
-            final int countInStrip = mSuggestionsCountInStrip;
-            setupTexts(suggestedWords, countInStrip);
-            mMoreSuggestionsAvailable = (suggestedWords.size() > countInStrip);
-            int x = 0;
-            for (int index = 0; index < countInStrip; index++) {
-                final int pos = getWordPosition(index, suggestedWords);
-
-                if (index != 0) {
-                    final View divider = mDividers.get(pos);
-                    // Add divider if this isn't the left most suggestion in suggestions strip.
-                    addDivider(stripView, divider);
-                    x += divider.getMeasuredWidth();
-                }
-
-                final CharSequence styled = mTexts.get(pos);
-                final TextView word = mWords.get(pos);
-                if (index == mCenterSuggestionIndex && mMoreSuggestionsAvailable) {
-                    // TODO: This "more suggestions hint" should have nicely designed icon.
-                    word.setCompoundDrawablesWithIntrinsicBounds(
-                            null, null, null, mMoreSuggestionsHint);
-                    // HACK: To align with other TextView that has no compound drawables.
-                    word.setCompoundDrawablePadding(-mMoreSuggestionsHint.getIntrinsicHeight());
-                } else {
-                    word.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
-                }
-
-                // Disable this suggestion if the suggestion is null or empty.
-                word.setEnabled(!TextUtils.isEmpty(styled));
-                word.setTextColor(getSuggestionTextColor(index, suggestedWords, pos));
-                final int width = getSuggestionWidth(index, stripWidth);
-                final CharSequence text = getEllipsizedText(styled, width, word.getPaint());
-                final float scaleX = word.getTextScaleX();
-                word.setText(text); // TextView.setText() resets text scale x to 1.0.
-                word.setTextScaleX(scaleX);
-                stripView.addView(word);
-                setLayoutWeight(
-                        word, getSuggestionWeight(index), ViewGroup.LayoutParams.MATCH_PARENT);
-                x += word.getMeasuredWidth();
-
-                if (DBG && pos < suggestedWords.size()) {
-                    final String debugInfo = Utils.getDebugInfo(suggestedWords, pos);
-                    if (debugInfo != null) {
-                        final TextView info = mInfos.get(pos);
-                        info.setText(debugInfo);
-                        placer.addView(info);
-                        info.measure(ViewGroup.LayoutParams.WRAP_CONTENT,
-                                ViewGroup.LayoutParams.WRAP_CONTENT);
-                        final int infoWidth = info.getMeasuredWidth();
-                        final int y = info.getMeasuredHeight();
-                        ViewLayoutUtils.placeViewAt(
-                                info, x - infoWidth, y, infoWidth, info.getMeasuredHeight());
-                    }
-                }
-            }
-        }
-
-        private int getSuggestionWidth(final int index, final int maxWidth) {
-            final int paddings = mPadding * mSuggestionsCountInStrip;
-            final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1);
-            final int availableWidth = maxWidth - paddings - dividers;
-            return (int)(availableWidth * getSuggestionWeight(index));
-        }
-
-        private float getSuggestionWeight(final int index) {
-            if (index == mCenterSuggestionIndex) {
-                return mCenterSuggestionWeight;
-            } else {
-                // TODO: Revisit this for cases of 5 or more suggestions
-                return (1.0f - mCenterSuggestionWeight) / (mSuggestionsCountInStrip - 1);
-            }
-        }
-
-        private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) {
-            mTexts.clear();
-            final int count = Math.min(suggestedWords.size(), countInStrip);
-            for (int pos = 0; pos < count; pos++) {
-                final CharSequence styled = getStyledSuggestionWord(suggestedWords, pos);
-                mTexts.add(styled);
-            }
-            for (int pos = count; pos < countInStrip; pos++) {
-                // Make this inactive for touches in layout().
-                mTexts.add(null);
-            }
-        }
-
-        private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords,
-                final ViewGroup stripView) {
-            final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP);
-            for (int index = 0; index < countInStrip; index++) {
-                if (index != 0) {
-                    // Add divider if this isn't the left most suggestion in suggestions strip.
-                    addDivider(stripView, mDividers.get(index));
-                }
-
-                final TextView word = mWords.get(index);
-                word.setEnabled(true);
-                word.setTextColor(mColorAutoCorrect);
-                final String text = suggestedWords.getWord(index);
-                word.setText(text);
-                word.setTextScaleX(1.0f);
-                word.setCompoundDrawables(null, null, null, null);
-                stripView.addView(word);
-                setLayoutWeight(word, 1.0f, mSuggestionsStripHeight);
-            }
-            mMoreSuggestionsAvailable = false;
-        }
-
-        public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView,
-                final int stripWidth, final CharSequence hintText, final OnClickListener listener) {
-            final int width = stripWidth - mDividerWidth - mPadding * 2;
-
-            final TextView wordView = mWordToSaveView;
-            wordView.setTextColor(mColorTypedWord);
-            final int wordWidth = (int)(width * mCenterSuggestionWeight);
-            final CharSequence text = getEllipsizedText(word, wordWidth, wordView.getPaint());
-            final float wordScaleX = wordView.getTextScaleX();
-            wordView.setTag(word);
-            wordView.setText(text);
-            wordView.setTextScaleX(wordScaleX);
-            stripView.addView(wordView);
-            setLayoutWeight(wordView, mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
-            stripView.addView(mDividers.get(0));
-
-            final TextView leftArrowView = mLeftwardsArrowView;
-            leftArrowView.setTextColor(mColorAutoCorrect);
-            leftArrowView.setText(LEFTWARDS_ARROW);
-            stripView.addView(leftArrowView);
-
-            final TextView hintView = mHintToSaveView;
-            hintView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
-            hintView.setTextColor(mColorAutoCorrect);
-            final int hintWidth = width - wordWidth - leftArrowView.getWidth();
-            final float hintScaleX = getTextScaleX(hintText, hintWidth, hintView.getPaint());
-            hintView.setText(hintText);
-            hintView.setTextScaleX(hintScaleX);
-            stripView.addView(hintView);
-            setLayoutWeight(
-                    hintView, 1.0f - mCenterSuggestionWeight, ViewGroup.LayoutParams.MATCH_PARENT);
-
-            wordView.setOnClickListener(listener);
-            leftArrowView.setOnClickListener(listener);
-            hintView.setOnClickListener(listener);
-        }
-
-        public CharSequence getAddToDictionaryWord() {
-            return (CharSequence)mWordToSaveView.getTag();
-        }
-
-        public boolean isAddToDictionaryShowing(final View v) {
-            return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView;
-        }
-
-        private static void setLayoutWeight(final View v, final float weight, final int height) {
-            final ViewGroup.LayoutParams lp = v.getLayoutParams();
-            if (lp instanceof LinearLayout.LayoutParams) {
-                final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp;
-                llp.weight = weight;
-                llp.width = 0;
-                llp.height = height;
-            }
-        }
-
-        private static float getTextScaleX(final CharSequence text, final int maxWidth,
-                final TextPaint paint) {
-            paint.setTextScaleX(1.0f);
-            final int width = getTextWidth(text, paint);
-            if (width <= maxWidth) {
-                return 1.0f;
-            }
-            return maxWidth / (float)width;
-        }
-
-        private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth,
-                final TextPaint paint) {
-            if (text == null) return null;
-            paint.setTextScaleX(1.0f);
-            final int width = getTextWidth(text, paint);
-            if (width <= maxWidth) {
-                return text;
-            }
-            final float scaleX = maxWidth / (float)width;
-            if (scaleX >= MIN_TEXT_XSCALE) {
-                paint.setTextScaleX(scaleX);
-                return text;
-            }
-
-            // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To
-            // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE).
-            final CharSequence ellipsized = TextUtils.ellipsize(
-                    text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE);
-            paint.setTextScaleX(MIN_TEXT_XSCALE);
-            return ellipsized;
-        }
-
-        private static int getTextWidth(final CharSequence text, final TextPaint paint) {
-            if (TextUtils.isEmpty(text)) return 0;
-            final Typeface savedTypeface = paint.getTypeface();
-            paint.setTypeface(getTextTypeface(text));
-            final int len = text.length();
-            final float[] widths = new float[len];
-            final int count = paint.getTextWidths(text, 0, len, widths);
-            int width = 0;
-            for (int i = 0; i < count; i++) {
-                width += Math.round(widths[i] + 0.5f);
-            }
-            paint.setTypeface(savedTypeface);
-            return width;
-        }
-
-        private static Typeface getTextTypeface(final CharSequence text) {
-            if (!(text instanceof SpannableString))
-                return Typeface.DEFAULT;
-
-            final SpannableString ss = (SpannableString)text;
-            final StyleSpan[] styles = ss.getSpans(0, text.length(), StyleSpan.class);
-            if (styles.length == 0)
-                return Typeface.DEFAULT;
-
-            switch (styles[0].getStyle()) {
-            case Typeface.BOLD: return Typeface.DEFAULT_BOLD;
-            // TODO: BOLD_ITALIC, ITALIC case?
-            default: return Typeface.DEFAULT;
-            }
-        }
-    }
+    private final SuggestionStripLayoutHelper mLayoutHelper;
 
     /**
      * Construct a {@link SuggestionStripView} for showing suggestions to be picked by the user.
@@ -579,19 +93,17 @@
         mSuggestionsStrip = (ViewGroup)findViewById(R.id.suggestions_strip);
         for (int pos = 0; pos < MAX_SUGGESTIONS; pos++) {
             final TextView word = (TextView)inflater.inflate(R.layout.suggestion_word, null);
-            word.setTag(pos);
             word.setOnClickListener(this);
             word.setOnLongClickListener(this);
-            mWords.add(word);
+            mWordViews.add(word);
             final View divider = inflater.inflate(R.layout.suggestion_divider, null);
-            divider.setTag(pos);
             divider.setOnClickListener(this);
-            mDividers.add(divider);
-            mInfos.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
+            mDividerViews.add(divider);
+            mDebugInfoViews.add((TextView)inflater.inflate(R.layout.suggestion_info, null));
         }
 
-        mParams = new SuggestionStripViewParams(
-                context, attrs, defStyle, mWords, mDividers, mInfos);
+        mLayoutHelper = new SuggestionStripLayoutHelper(
+                context, attrs, defStyle, mWordViews, mDividerViews, mDebugInfoViews);
 
         mMoreSuggestionsContainer = inflater.inflate(R.layout.more_suggestions, null);
         mMoreSuggestionsView = (MoreSuggestionsView)mMoreSuggestionsContainer
@@ -617,24 +129,25 @@
     public void setSuggestions(final SuggestedWords suggestedWords) {
         clear();
         mSuggestedWords = suggestedWords;
-        mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth());
+        mLayoutHelper.layout(mSuggestedWords, mSuggestionsStrip, this);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords);
         }
     }
 
     public int setMoreSuggestionsHeight(final int remainingHeight) {
-        return mParams.setMoreSuggestionsHeight(remainingHeight);
+        return mLayoutHelper.setMoreSuggestionsHeight(remainingHeight);
     }
 
     public boolean isShowingAddToDictionaryHint() {
         return mSuggestionsStrip.getChildCount() > 0
-                && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
+                && mLayoutHelper.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0));
     }
 
     public void showAddToDictionaryHint(final String word, final CharSequence hintText) {
         clear();
-        mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this);
+        mLayoutHelper.layoutAddToDictionaryHint(
+                word, mSuggestionsStrip, getWidth(), hintText, this);
     }
 
     public boolean dismissAddToDictionaryHint() {
@@ -649,27 +162,27 @@
         mSuggestionsStrip.removeAllViews();
         removeAllViews();
         addView(mSuggestionsStrip);
-        dismissMoreSuggestions();
+        mMoreSuggestionsView.dismissMoreKeysPanel();
     }
 
     private final MoreSuggestionsListener mMoreSuggestionsListener = new MoreSuggestionsListener() {
         @Override
         public void onSuggestionSelected(final int index, final SuggestedWordInfo wordInfo) {
             mListener.pickSuggestionManually(index, wordInfo);
-            dismissMoreSuggestions();
+            mMoreSuggestionsView.dismissMoreKeysPanel();
         }
 
         @Override
         public void onCancelInput() {
-            dismissMoreSuggestions();
+            mMoreSuggestionsView.dismissMoreKeysPanel();
         }
     };
 
     private final MoreKeysPanel.Controller mMoreSuggestionsController =
             new MoreKeysPanel.Controller() {
         @Override
-        public boolean onDismissMoreKeysPanel() {
-            return mMainKeyboardView.onDismissMoreKeysPanel();
+        public void onDismissMoreKeysPanel(final MoreKeysPanel panel) {
+            mMainKeyboardView.onDismissMoreKeysPanel(panel);
         }
 
         @Override
@@ -678,18 +191,15 @@
         }
 
         @Override
-        public void onCancelMoreKeysPanel() {
-            dismissMoreSuggestions();
+        public void onCancelMoreKeysPanel(final MoreKeysPanel panel) {
+            mMoreSuggestionsView.dismissMoreKeysPanel();
         }
     };
 
-    boolean dismissMoreSuggestions() {
-        return mMoreSuggestionsView.dismissMoreKeysPanel();
-    }
-
     @Override
     public boolean onLongClick(final View view) {
-        KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE);
+        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+                Constants.NOT_A_CODE, this);
         return showMoreSuggestions();
     }
 
@@ -698,30 +208,30 @@
         if (parentKeyboard == null) {
             return false;
         }
-        final SuggestionStripViewParams params = mParams;
-        if (!params.mMoreSuggestionsAvailable) {
+        final SuggestionStripLayoutHelper layoutHelper = mLayoutHelper;
+        if (!layoutHelper.mMoreSuggestionsAvailable) {
             return false;
         }
         final int stripWidth = getWidth();
         final View container = mMoreSuggestionsContainer;
         final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
         final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
-        builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
-                (int)(maxWidth * params.mMinMoreSuggestionsWidth),
-                params.getMaxMoreSuggestionsRow(), parentKeyboard);
+        builder.layout(mSuggestedWords, layoutHelper.mSuggestionsCountInStrip, maxWidth,
+                (int)(maxWidth * layoutHelper.mMinMoreSuggestionsWidth),
+                layoutHelper.getMaxMoreSuggestionsRow(), parentKeyboard);
         mMoreSuggestionsView.setKeyboard(builder.build());
         container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
 
         final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
         final int pointX = stripWidth / 2;
-        final int pointY = -params.mMoreSuggestionsBottomGap;
+        final int pointY = -layoutHelper.mMoreSuggestionsBottomGap;
         moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
                 mMoreSuggestionsListener);
         mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
         mOriginX = mLastX;
         mOriginY = mLastY;
-        for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
-            mWords.get(i).setPressed(false);
+        for (int i = 0; i < layoutHelper.mSuggestionsCountInStrip; i++) {
+            mWordViews.get(i).setPressed(false);
         }
         return true;
     }
@@ -791,18 +301,23 @@
 
     @Override
     public void onClick(final View view) {
-        if (mParams.isAddToDictionaryShowing(view)) {
-            mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString());
+        if (mLayoutHelper.isAddToDictionaryShowing(view)) {
+            mListener.addWordToUserDictionary(mLayoutHelper.getAddToDictionaryWord());
             clear();
             return;
         }
 
         final Object tag = view.getTag();
-        if (!(tag instanceof Integer))
+        // Integer tag is set at
+        // {@link SuggestionStripLayoutHelper#setupWordViewsTextAndColor(SuggestedWords,int)} and
+        // {@link SuggestionStripLayoutHelper#layoutPunctuationSuggestions(SuggestedWords,ViewGroup}
+        if (!(tag instanceof Integer)) {
             return;
+        }
         final int index = (Integer) tag;
-        if (index >= mSuggestedWords.size())
+        if (index >= mSuggestedWords.size()) {
             return;
+        }
 
         final SuggestedWordInfo wordInfo = mSuggestedWords.getInfo(index);
         mListener.pickSuggestionManually(index, wordInfo);
@@ -811,6 +326,6 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        dismissMoreSuggestions();
+        mMoreSuggestionsView.dismissMoreKeysPanel();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
index 2b6fda3..21426d1 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java
@@ -16,10 +16,6 @@
 
 package com.android.inputmethod.latin.userdictionary;
 
-import com.android.inputmethod.compat.UserDictionaryCompatUtils;
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-
 import android.app.Activity;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -30,6 +26,10 @@
 import android.view.View;
 import android.widget.EditText;
 
+import com.android.inputmethod.compat.UserDictionaryCompatUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+
 import java.util.ArrayList;
 import java.util.Locale;
 import java.util.TreeSet;
@@ -65,6 +65,8 @@
     private String mLocale;
     private final String mOldWord;
     private final String mOldShortcut;
+    private String mSavedWord;
+    private String mSavedShortcut;
 
     /* package */ UserDictionaryAddWordContents(final View view, final Bundle args) {
         mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
@@ -76,7 +78,9 @@
         final String word = args.getString(EXTRA_WORD);
         if (null != word) {
             mWordEditText.setText(word);
-            mWordEditText.setSelection(word.length());
+            // Use getText in case the edit text modified the text we set. This happens when
+            // it's too long to be edited.
+            mWordEditText.setSelection(mWordEditText.getText().length());
         }
         final String shortcut;
         if (UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
@@ -94,6 +98,16 @@
         updateLocale(args.getString(EXTRA_LOCALE));
     }
 
+    /* package */ UserDictionaryAddWordContents(final View view,
+            final UserDictionaryAddWordContents oldInstanceToBeEdited) {
+        mWordEditText = (EditText)view.findViewById(R.id.user_dictionary_add_word_text);
+        mShortcutEditText = (EditText)view.findViewById(R.id.user_dictionary_add_shortcut);
+        mMode = MODE_EDIT;
+        mOldWord = oldInstanceToBeEdited.mSavedWord;
+        mOldShortcut = oldInstanceToBeEdited.mSavedShortcut;
+        updateLocale(mLocale);
+    }
+
     // locale may be null, this means default locale
     // It may also be the empty string, which means "all locales"
     /* package */ void updateLocale(final String locale) {
@@ -147,6 +161,8 @@
             // If the word is somehow empty, don't insert it.
             return CODE_CANCEL;
         }
+        mSavedWord = newWord;
+        mSavedShortcut = newShortcut;
         // If there is no shortcut, and the word already exists in the database, then we
         // should not insert, because either A. the word exists with no shortcut, in which
         // case the exact same thing we want to insert is already there, or B. the word
@@ -258,4 +274,8 @@
         localesList.add(new LocaleRenderer(activity, null)); // meaning: select another locale
         return localesList;
     }
+
+    public String getCurrentUserDictionaryLocale() {
+        return mLocale;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
index 58c8f26..4fc132f 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java
@@ -57,23 +57,39 @@
     private boolean mIsDeleting = false;
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
+    public void onActivityCreated(final Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
         setHasOptionsMenu(true);
+        getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary);
+        // Keep the instance so that we remember mContents when configuration changes (eg rotation)
+        setRetainInstance(true);
     }
 
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
+    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
+            final Bundle savedState) {
         mRootView = inflater.inflate(R.layout.user_dictionary_add_word_fullscreen, null);
         mIsDeleting = false;
+        // If we have a non-null mContents object, it's the old value before a configuration
+        // change (eg rotation) so we need to use its values. Otherwise, read from the arguments.
         if (null == mContents) {
             mContents = new UserDictionaryAddWordContents(mRootView, getArguments());
+        } else {
+            // We create a new mContents object to account for the new situation : a word has
+            // been added to the user dictionary when we started rotating, and we are now editing
+            // it. That means in particular if the word undergoes any change, the old version should
+            // be updated, so the mContents object needs to switch to EDIT mode if it was in
+            // INSERT mode.
+            mContents = new UserDictionaryAddWordContents(mRootView,
+                    mContents /* oldInstanceToBeEdited */);
         }
+        getActivity().getActionBar().setSubtitle(UserDictionarySettingsUtils.getLocaleDisplayName(
+                getActivity(), mContents.getCurrentUserDictionaryLocale()));
         return mRootView;
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
         final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0,
                 R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add);
         actionItemAdd.setShowAsAction(
@@ -87,7 +103,7 @@
     /**
      * Callback for the framework when a menu option is pressed.
      *
-     * @param MenuItem the item that was pressed
+     * @param item the item that was pressed
      * @return false to allow normal menu processing to proceed, true to consume it here
      */
     @Override
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
index 6e64882..32c4950 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java
@@ -16,10 +16,8 @@
 
 package com.android.inputmethod.latin.userdictionary;
 
-import com.android.inputmethod.latin.LocaleUtils;
-import com.android.inputmethod.latin.R;
-
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.os.Bundle;
@@ -28,7 +26,14 @@
 import android.preference.PreferenceGroup;
 import android.provider.UserDictionary;
 import android.text.TextUtils;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+
+import java.util.List;
 import java.util.Locale;
 import java.util.TreeSet;
 
@@ -52,8 +57,7 @@
         final Cursor cursor = activity.managedQuery(UserDictionary.Words.CONTENT_URI,
                 new String[] { UserDictionary.Words.LOCALE },
                 null, null, null);
-        final TreeSet<String> localeList = new TreeSet<String>();
-        boolean addedAllLocale = false;
+        final TreeSet<String> localeSet = new TreeSet<String>();
         if (null == cursor) {
             // The user dictionary service is not present or disabled. Return null.
             return null;
@@ -61,20 +65,39 @@
             final int columnIndex = cursor.getColumnIndex(UserDictionary.Words.LOCALE);
             do {
                 final String locale = cursor.getString(columnIndex);
-                final boolean allLocale = TextUtils.isEmpty(locale);
-                localeList.add(allLocale ? "" : locale);
-                if (allLocale) {
-                    addedAllLocale = true;
-                }
+                localeSet.add(null != locale ? locale : "");
             } while (cursor.moveToNext());
         }
-        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED && !addedAllLocale) {
+        if (!UserDictionarySettings.IS_SHORTCUT_API_SUPPORTED) {
             // For ICS, we need to show "For all languages" in case that the keyboard locale
             // is different from the system locale
-            localeList.add("");
+            localeSet.add("");
         }
-        localeList.add(Locale.getDefault().toString());
-        return localeList;
+
+        final InputMethodManager imm =
+                (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+        final List<InputMethodInfo> imis = imm.getEnabledInputMethodList();
+        for (final InputMethodInfo imi : imis) {
+            final List<InputMethodSubtype> subtypes =
+                    imm.getEnabledInputMethodSubtypeList(
+                            imi, true /* allowsImplicitlySelectedSubtypes */);
+            for (InputMethodSubtype subtype : subtypes) {
+                final String locale = subtype.getLocale();
+                if (!TextUtils.isEmpty(locale)) {
+                    localeSet.add(locale);
+                }
+            }
+        }
+
+        // We come here after we have collected locales from existing user dictionary entries and
+        // enabled subtypes. If we already have the locale-without-country version of the system
+        // locale, we don't add the system locale to avoid confusion even though it's technically
+        // correct to add it.
+        if (!localeSet.contains(Locale.getDefault().getLanguage().toString())) {
+            localeSet.add(Locale.getDefault().toString());
+        }
+
+        return localeSet;
     }
 
     /**
@@ -84,13 +107,19 @@
     protected void createUserDictSettings(PreferenceGroup userDictGroup) {
         final Activity activity = getActivity();
         userDictGroup.removeAll();
-        final TreeSet<String> localeList =
+        final TreeSet<String> localeSet =
                 UserDictionaryList.getUserDictionaryLocalesSet(activity);
 
-        if (localeList.isEmpty()) {
+        if (localeSet.size() > 1) {
+            // Have an "All languages" entry in the languages list if there are two or more active
+            // languages
+            localeSet.add("");
+        }
+
+        if (localeSet.isEmpty()) {
             userDictGroup.addPreference(createUserDictionaryPreference(null, activity));
         } else {
-            for (String locale : localeList) {
+            for (String locale : localeSet) {
                 userDictGroup.addPreference(createUserDictionaryPreference(locale, activity));
             }
         }
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
index 50dda96..7571e87 100644
--- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java
@@ -1,17 +1,17 @@
-/**
- * Copyright (C) 2013 Google Inc.
+/*
+ * 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
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
  *
- * http://www.apache.org/licenses/LICENSE-2.0
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, 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.
+ * 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.userdictionary;
@@ -150,7 +150,9 @@
         listView.setEmptyView(emptyView);
 
         setHasOptionsMenu(true);
-
+        // Show the language as a subtitle of the action bar
+        getActivity().getActionBar().setSubtitle(
+                UserDictionarySettingsUtils.getLocaleDisplayName(getActivity(), mLocale));
     }
 
     @SuppressWarnings("deprecation")
diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
new file mode 100644
index 0000000..e58727e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettingsUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.userdictionary;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.LocaleUtils;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.util.Locale;
+
+/**
+ * Utilities of the user dictionary settings
+ * TODO: We really want to move these utilities to a static library.
+ */
+public class UserDictionarySettingsUtils {
+    public static String getLocaleDisplayName(Context context, String localeStr) {
+        if (TextUtils.isEmpty(localeStr)) {
+            // CAVEAT: localeStr should not be null because a null locale stands for the system
+            // locale in UserDictionary.Words.addWord.
+            return context.getResources().getString(R.string.user_dict_settings_all_languages);
+        }
+        final Locale locale = LocaleUtils.constructLocaleFromString(localeStr);
+        final Locale systemLocale = context.getResources().getConfiguration().locale;
+        return locale.getDisplayName(systemLocale);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
similarity index 81%
rename from java/src/com/android/inputmethod/latin/AdditionalSubtype.java
rename to java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
index 99b95ea..215faa0 100644
--- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java
+++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE;
 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE;
@@ -25,12 +25,14 @@
 import android.text.TextUtils;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.latin.R;
+
 import java.util.ArrayList;
 
-public final class AdditionalSubtype {
+public final class AdditionalSubtypeUtils {
     private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0];
 
-    private AdditionalSubtype() {
+    private AdditionalSubtypeUtils() {
         // This utility class is not publicly instantiable.
     }
 
@@ -46,17 +48,18 @@
         final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
         final String layoutDisplayNameExtraValue;
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
-                && SubtypeLocale.isExceptionalLocale(localeString)) {
-            final String layoutDisplayName = SubtypeLocale.getKeyboardLayoutSetDisplayName(
+                && SubtypeLocaleUtils.isExceptionalLocale(localeString)) {
+            final String layoutDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(
                     keyboardLayoutSetName);
-            layoutDisplayNameExtraValue = StringUtils.appendToCsvIfNotExists(
+            layoutDisplayNameExtraValue = StringUtils.appendToCommaSplittableTextIfNotExists(
                     UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + layoutDisplayName, extraValue);
         } else {
             layoutDisplayNameExtraValue = extraValue;
         }
-        final String additionalSubtypeExtraValue = StringUtils.appendToCsvIfNotExists(
-                IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
-        final int nameId = SubtypeLocale.getSubtypeNameId(localeString, keyboardLayoutSetName);
+        final String additionalSubtypeExtraValue =
+                StringUtils.appendToCommaSplittableTextIfNotExists(
+                        IS_ADDITIONAL_SUBTYPE, layoutDisplayNameExtraValue);
+        final int nameId = SubtypeLocaleUtils.getSubtypeNameId(localeString, keyboardLayoutSetName);
         return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard,
                 localeString, KEYBOARD_MODE,
                 layoutExtraValue + "," + additionalSubtypeExtraValue, false, false);
@@ -64,10 +67,11 @@
 
     public static String getPrefSubtype(final InputMethodSubtype subtype) {
         final String localeString = subtype.getLocale();
-        final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype);
+        final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
         final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
-        final String extraValue = StringUtils.removeFromCsvIfExists(layoutExtraValue,
-                StringUtils.removeFromCsvIfExists(IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
+        final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
+                layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
+                        IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
         final String basePrefSubtype = localeString + LOCALE_AND_LAYOUT_SEPARATOR
                 + keyboardLayoutSetName;
         return extraValue.isEmpty() ? basePrefSubtype
@@ -94,7 +98,7 @@
                 CollectionUtils.newArrayList(prefSubtypeArray.length);
         for (final String prefSubtype : prefSubtypeArray) {
             final InputMethodSubtype subtype = createAdditionalSubtype(prefSubtype);
-            if (subtype.getNameResId() == SubtypeLocale.UNKNOWN_KEYBOARD_LAYOUT) {
+            if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT) {
                 // Skip unknown keyboard layout subtype. This may happen when predefined keyboard
                 // layout has been removed.
                 continue;
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
new file mode 100644
index 0000000..08a2a8c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * 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.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+public final class ApplicationUtils {
+    private static final String TAG = ApplicationUtils.class.getSimpleName();
+
+    private ApplicationUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static int getAcitivityTitleResId(final Context context,
+            final Class<? extends Activity> cls) {
+        final ComponentName cn = new ComponentName(context, cls);
+        try {
+            final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
+            if (ai != null) {
+                return ai.labelRes;
+            }
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Failed to get settings activity title res id.", e);
+        }
+        return 0;
+    }
+
+    /**
+     * A utility method to get the application's PackageInfo.versionName
+     * @return the application's PackageInfo.versionName
+     */
+    public static String getVersionName(final Context context) {
+        try {
+            if (context == null) {
+                return "";
+            }
+            final String packageName = context.getPackageName();
+            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionName;
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Could not find version info.", e);
+        }
+        return "";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
similarity index 88%
rename from java/src/com/android/inputmethod/latin/AutoCorrection.java
rename to java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
index fa35922..066c5fd 100644
--- a/java/src/com/android/inputmethod/latin/AutoCorrection.java
+++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java
@@ -14,8 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
+import com.android.inputmethod.latin.BinaryDictionary;
+import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import android.text.TextUtils;
@@ -23,21 +27,22 @@
 
 import java.util.concurrent.ConcurrentHashMap;
 
-public final class AutoCorrection {
+public final class AutoCorrectionUtils {
     private static final boolean DBG = LatinImeLogger.sDBG;
-    private static final String TAG = AutoCorrection.class.getSimpleName();
+    private static final String TAG = AutoCorrectionUtils.class.getSimpleName();
     private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
 
-    private AutoCorrection() {
+    private AutoCorrectionUtils() {
         // Purely static class: can't instantiate.
     }
 
-    public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word, final boolean ignoreCase) {
+    public static boolean isValidWord(final Suggest suggest, final String word,
+            final boolean ignoreCase) {
         if (TextUtils.isEmpty(word)) {
             return false;
         }
-        final String lowerCasedWord = word.toLowerCase();
+        final ConcurrentHashMap<String, Dictionary> dictionaries = suggest.getUnigramDictionaries();
+        final String lowerCasedWord = word.toLowerCase(suggest.mLocale);
         for (final String key : dictionaries.keySet()) {
             final Dictionary dictionary = dictionaries.get(key);
             // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow
@@ -73,13 +78,6 @@
         return maxFreq;
     }
 
-    // Returns true if this is in any of the dictionaries.
-    public static boolean isInTheDictionary(
-            final ConcurrentHashMap<String, Dictionary> dictionaries,
-            final String word, final boolean ignoreCase) {
-        return isValidWord(dictionaries, word, ignoreCase);
-    }
-
     public static boolean suggestionExceedsAutoCorrectionThreshold(
             final SuggestedWordInfo suggestion, final String consideredWord,
             final float autoCorrectionThreshold) {
diff --git a/java/src/com/android/inputmethod/latin/utils/Base64Reader.java b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java
new file mode 100644
index 0000000..3eca6e7
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java
@@ -0,0 +1,117 @@
+/*
+ * 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.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.LineNumberReader;
+
+@UsedForTesting
+public class Base64Reader {
+    private final LineNumberReader mReader;
+
+    private String mLine;
+    private int mCharPos;
+    private int mByteCount;
+
+    @UsedForTesting
+    public Base64Reader(final LineNumberReader reader) {
+        mReader = reader;
+        reset();
+    }
+
+    @UsedForTesting
+    public void reset() {
+        mLine = null;
+        mCharPos = 0;
+        mByteCount = 0;
+    }
+
+    @UsedForTesting
+    public int getLineNumber() {
+        return mReader.getLineNumber();
+    }
+
+    @UsedForTesting
+    public int getByteCount() {
+        return mByteCount;
+    }
+
+    private void fillBuffer() throws IOException {
+        if (mLine == null || mCharPos >= mLine.length()) {
+            mLine = mReader.readLine();
+            mCharPos = 0;
+        }
+        if (mLine == null) {
+            throw new EOFException();
+        }
+    }
+
+    private int peekUint8() throws IOException {
+        fillBuffer();
+        final char c = mLine.charAt(mCharPos);
+        if (c >= 'A' && c <= 'Z')
+            return c - 'A' + 0;
+        if (c >= 'a' && c <= 'z')
+            return c - 'a' + 26;
+        if (c >= '0' && c <= '9')
+            return c - '0' + 52;
+        if (c == '+')
+            return 62;
+        if (c == '/')
+            return 63;
+        if (c == '=')
+            return 0;
+        throw new RuntimeException("Unknown character '" + c + "' in base64 at line "
+                + mReader.getLineNumber());
+    }
+
+    private int getUint8() throws IOException {
+        final int value = peekUint8();
+        mCharPos++;
+        return value;
+    }
+
+    @UsedForTesting
+    public int readUint8() throws IOException {
+        final int value1, value2;
+        switch (mByteCount % 3) {
+        case 0:
+            value1 = getUint8() << 2;
+            value2 = value1 | (peekUint8() >> 4);
+            break;
+        case 1:
+            value1 = (getUint8() & 0x0f) << 4;
+            value2 = value1 | (peekUint8() >> 2);
+            break;
+        default:
+            value1 = (getUint8() & 0x03) << 6;
+            value2 = value1 | getUint8();
+            break;
+        }
+        mByteCount++;
+        return value2;
+    }
+
+    @UsedForTesting
+    public short readInt16() throws IOException {
+        final int data = readUint8() << 8;
+        return (short)(data | readUint8());
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/BoundedTreeSet.java
rename to java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
index 489a74e..ae1fd3f 100644
--- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java
+++ b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
diff --git a/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java b/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
new file mode 100644
index 0000000..1bb27aa
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ByteArrayWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * 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.utils;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
+
+/**
+ * This class provides an implementation for the FusionDictionary buffer interface that is backed
+ * by a simpled byte array. It allows to create a binary dictionary in memory.
+ */
+public final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
+    private byte[] mBuffer;
+    private int mPosition;
+
+    public ByteArrayWrapper(final byte[] buffer) {
+        mBuffer = buffer;
+        mPosition = 0;
+    }
+
+    @Override
+    public int readUnsignedByte() {
+        return mBuffer[mPosition++] & 0xFF;
+    }
+
+    @Override
+    public int readUnsignedShort() {
+        final int retval = readUnsignedByte();
+        return (retval << 8) + readUnsignedByte();
+    }
+
+    @Override
+    public int readUnsignedInt24() {
+        final int retval = readUnsignedShort();
+        return (retval << 8) + readUnsignedByte();
+    }
+
+    @Override
+    public int readInt() {
+        final int retval = readUnsignedShort();
+        return (retval << 16) + readUnsignedShort();
+    }
+
+    @Override
+    public int position() {
+        return mPosition;
+    }
+
+    @Override
+    public void position(int position) {
+        mPosition = position;
+    }
+
+    @Override
+    public void put(final byte b) {
+        mBuffer[mPosition++] = b;
+    }
+
+    @Override
+    public int limit() {
+        return mBuffer.length - 1;
+    }
+
+    @Override
+    public int capacity() {
+        return mBuffer.length;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/CapsModeUtils.java
rename to java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
index 4b8d1ac..2f91c57 100644
--- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java
@@ -14,11 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.text.InputType;
 import android.text.TextUtils;
 
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.WordComposer;
+
 import java.util.Locale;
 
 public final class CapsModeUtils {
diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/CollectionUtils.java
rename to java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
index a8623cc..98f0d8b 100644
--- a/java/src/com/android/inputmethod/latin/CollectionUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.util.SparseArray;
 
diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
rename to java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java
index 792a446..5ccf0e0 100644
--- a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.text.TextUtils;
 import android.view.inputmethod.CompletionInfo;
diff --git a/java/src/com/android/inputmethod/latin/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/CoordinateUtils.java
rename to java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
index af270e1..72f2cd2 100644
--- a/java/src/com/android/inputmethod/latin/CoordinateUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 public final class CoordinateUtils {
     private static final int INDEX_X = 0;
diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
new file mode 100644
index 0000000..36b927e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java
@@ -0,0 +1,318 @@
+/*
+ * 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.utils;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Utility methods for parsing and serializing Comma-Separated Values. The public APIs of this
+ * utility class are {@link #split(String)}, {@link #split(int,String)}, {@link #join(String...)},
+ * {@link #join(int,String...)}, and {@link #join(int,int[],String...)}.
+ *
+ * This class implements CSV parsing and serializing methods conforming to RFC 4180 with an
+ * exception:
+ *  These methods can't handle new line code escaped in double quotes.
+ */
+@UsedForTesting
+public final class CsvUtils {
+    private CsvUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static final int SPLIT_FLAGS_NONE = 0x0;
+    /**
+     * A flag for {@link #split(int,String)}. If this flag is specified, the method will trim
+     * spaces around fields before splitting. Note that this behavior doesn't conform to RFC 4180.
+     */
+    public static final int SPLIT_FLAGS_TRIM_SPACES  = 0x1;
+
+    public static final int JOIN_FLAGS_NONE = 0x0;
+    /**
+     * A flag for {@link #join(int,String...)} and {@link #join(int,int[],String...)}. If this
+     * flag is specified, these methods surround each field with double quotes before joining.
+     */
+    public static final int JOIN_FLAGS_ALWAYS_QUOTED = 0x1;
+    /**
+     * A flag for {@link #join(int,String...)} and {@link #join(int,int[],String...)}. If this
+     * flag is specified, these methods add an extra space just after the comma separator. Note that
+     * this behavior doesn't conform to RFC 4180.
+     */
+    public static final int JOIN_FLAGS_EXTRA_SPACE   = 0x2;
+
+    // Note that none of these characters match high or low surrogate characters, so we need not
+    // take care of matching by code point.
+    private static final char COMMA = ',';
+    private static final char SPACE = ' ';
+    private static final char QUOTE = '"';
+
+    @SuppressWarnings("serial")
+    public static class CsvParseException extends RuntimeException {
+        public CsvParseException(final String message) {
+            super(message);
+        }
+    }
+
+    /**
+     * Find the first non-space character in the text.
+     *
+     * @param text the text to be searched.
+     * @param fromIndex the index to start the search from, inclusive.
+     * @return the index of the first occurrence of the non-space character in the
+     * <code>text</code> that is greater than or equal to <code>fromIndex</code>, or the length of
+     * the <code>text</code> if the character does not occur.
+     */
+    private static int indexOfNonSpace(final String text, final int fromIndex) {
+        final int length = text.length();
+        if (fromIndex < 0 || fromIndex > length) {
+            throw new IllegalArgumentException("text=" + text + " fromIndex=" + fromIndex);
+        }
+        int index = fromIndex;
+        while (index < length && text.charAt(index) == SPACE) {
+            index++;
+        }
+        return index;
+    }
+
+    /**
+     * Find the last non-space character in the text.
+     *
+     * @param text the text to be searched.
+     * @param fromIndex the index to start the search from, exclusive.
+     * @param toIndex the index to end the search at, inclusive. Usually <code>toIndex</code>
+     * points a non-space character.
+     * @return the index of the last occurrence of the non-space character in the
+     * <code>text</code>, exclusive. It is less than <code>fromIndex</code> and greater than
+     * <code>toIndex</code>, or <code>toIndex</code> if the character does not occur.
+     */
+    private static int lastIndexOfNonSpace(final String text, final int fromIndex,
+            final int toIndex) {
+        if (toIndex < 0 || fromIndex > text.length() || fromIndex < toIndex) {
+            throw new IllegalArgumentException(
+                    "text=" + text + " fromIndex=" + fromIndex + " toIndex=" + toIndex);
+        }
+        int index = fromIndex;
+        while (index > toIndex && text.charAt(index - 1) == SPACE) {
+            index--;
+        }
+        return index;
+    }
+
+    /**
+     * Find the index of a comma separator. The search takes account of quoted fields and escape
+     * quotes.
+     *
+     * @param text the text to be searched.
+     * @param fromIndex the index to start the search from, inclusive.
+     * @return the index of the comma separator, exclusive.
+     */
+    private static int indexOfSeparatorComma(final String text, final int fromIndex) {
+        final int length = text.length();
+        if (fromIndex < 0 || fromIndex > length) {
+            throw new IllegalArgumentException("text=" + text + " fromIndex=" + fromIndex);
+        }
+        final boolean isQuoted = (length - fromIndex > 0 && text.charAt(fromIndex) == QUOTE);
+        for (int index = fromIndex + (isQuoted ? 1 : 0); index < length; index++) {
+            final char c = text.charAt(index);
+            if (c == COMMA && !isQuoted) {
+                return index;
+            }
+            if (c == QUOTE) {
+                final int nextIndex = index + 1;
+                if (nextIndex < length && text.charAt(nextIndex) == QUOTE) {
+                    // Quoted quote.
+                    index = nextIndex;
+                    continue;
+                }
+                // Closing quote.
+                final int endIndex = text.indexOf(COMMA, nextIndex);
+                return endIndex < 0 ? length : endIndex;
+            }
+        }
+        return length;
+    }
+
+    /**
+     * Removing any enclosing QUOTEs (U+0022), and convert any two consecutive QUOTEs into
+     * one QUOTE.
+     *
+     * @param text the CSV field text that may have enclosing QUOTEs and escaped QUOTE character.
+     * @return the text that has been removed enclosing quotes and converted two consecutive QUOTEs
+     * into one QUOTE.
+     */
+    @UsedForTesting
+    /* private */ static String unescapeField(final String text) {
+        StringBuilder sb = null;
+        final int length = text.length();
+        final boolean isQuoted = (length > 0 && text.charAt(0) == QUOTE);
+        int start = isQuoted ? 1 : 0;
+        int end = start;
+        while (start <= length && (end = text.indexOf(QUOTE, start)) >= start) {
+            final int nextIndex = end + 1;
+            if (nextIndex == length && isQuoted) {
+                // Closing quote.
+                break;
+            }
+            if (nextIndex < length && text.charAt(nextIndex) == QUOTE) {
+                if (!isQuoted) {
+                    throw new CsvParseException("Escaped quote in text");
+                }
+                // Quoted quote.
+                if (sb == null) {
+                    sb = new StringBuilder();
+                }
+                sb.append(text.substring(start, nextIndex));
+                start = nextIndex + 1;
+            } else {
+                throw new CsvParseException(
+                        isQuoted ? "Raw quote in quoted text" : "Raw quote in text");
+            }
+        }
+        if (end < 0 && isQuoted) {
+            throw new CsvParseException("Unterminated quote");
+        }
+        if (end < 0) {
+            end = length;
+        }
+        if (sb != null && start < length) {
+            sb.append(text.substring(start, end));
+        }
+        return sb == null ? text.substring(start, end) : sb.toString();
+    }
+
+    /**
+     * Split the CSV text into fields. The leading and trailing spaces of the each field can be
+     * trimmed optionally.
+     *
+     * @param splitFlags flags for split behavior. {@link #SPLIT_FLAGS_TRIM_SPACES} will trim
+     * spaces around each fields.
+     * @param line the text of CSV fields.
+     * @return the array of unescaped CVS fields.
+     * @throws CsvParseException
+     */
+    @UsedForTesting
+    public static String[] split(final int splitFlags, final String line) throws CsvParseException {
+        final boolean trimSpaces = (splitFlags & SPLIT_FLAGS_TRIM_SPACES) != 0;
+        final ArrayList<String> fields = CollectionUtils.newArrayList();
+        final int length = line.length();
+        int start = 0;
+        do {
+            final int csvStart = trimSpaces ? indexOfNonSpace(line, start) : start;
+            final int end = indexOfSeparatorComma(line, csvStart);
+            final int csvEnd = trimSpaces ? lastIndexOfNonSpace(line, end, csvStart) : end;
+            final String csvText = unescapeField(line.substring(csvStart, csvEnd));
+            fields.add(csvText);
+            start = end + 1;
+        } while (start <= length);
+        return fields.toArray(new String[fields.size()]);
+    }
+
+    @UsedForTesting
+    public static String[] split(final String line) throws CsvParseException {
+        return split(SPLIT_FLAGS_NONE, line);
+    }
+
+    /**
+     * Convert the raw CSV field text to the escaped text. It adds enclosing QUOTEs (U+0022) if the
+     * raw value contains any QUOTE or comma. Also it converts any QUOTE character into two
+     * consecutive QUOTE characters.
+     *
+     * @param text the raw CSV field text to be escaped.
+     * @param alwaysQuoted true if the escaped text should always be enclosed by QUOTEs.
+     * @return the escaped text.
+     */
+    @UsedForTesting
+    /* private */ static String escapeField(final String text, final boolean alwaysQuoted) {
+        StringBuilder sb = null;
+        boolean needsQuoted = alwaysQuoted;
+        final int length = text.length();
+        int indexToBeAppended = 0;
+        for (int index = indexToBeAppended; index < length; index++) {
+            final char c = text.charAt(index);
+            if (c == COMMA) {
+                needsQuoted = true;
+            } else if (c == QUOTE) {
+                needsQuoted = true;
+                if (sb == null) {
+                    sb = new StringBuilder();
+                }
+                sb.append(text.substring(indexToBeAppended, index));
+                indexToBeAppended = index + 1;
+                sb.append(QUOTE); // escaping quote.
+                sb.append(QUOTE); // escaped quote.
+            }
+        }
+        if (sb != null && indexToBeAppended < length) {
+            sb.append(text.substring(indexToBeAppended));
+        }
+        final String escapedText = (sb == null) ? text : sb.toString();
+        return needsQuoted ? QUOTE + escapedText + QUOTE : escapedText;
+    }
+
+    private static final String SPACES = "                    ";
+
+    private static void padToColumn(final StringBuilder sb, final int column) {
+        int padding;
+        while ((padding = column - sb.length()) > 0) {
+            final String spaces = SPACES.substring(0, Math.min(padding, SPACES.length()));
+            sb.append(spaces);
+        }
+    }
+
+    /**
+     * Join CSV text fields with comma. The column positions of the fields can be specified
+     * optionally. Surround each fields with double quotes before joining.
+     *
+     * @param joinFlags flags for join behavior. {@link #JOIN_FLAGS_EXTRA_SPACE} will add an extra
+     * space after each comma separator. {@link #JOIN_FLAGS_ALWAYS_QUOTED} will always add
+     * surrounding quotes to each element.
+     * @param columnPositions the array of column positions of the fields. It can be shorter than
+     * <code>fields</code> or null. Note that specifying the array column positions of the fields
+     * doesn't conform to RFC 4180.
+     * @param fields the CSV text fields.
+     * @return the string of the joined and escaped <code>fields</code>.
+     */
+    @UsedForTesting
+    public static String join(final int joinFlags, final int columnPositions[],
+            final String... fields) {
+        final boolean alwaysQuoted = (joinFlags & JOIN_FLAGS_ALWAYS_QUOTED) != 0;
+        final String separator = COMMA + ((joinFlags & JOIN_FLAGS_EXTRA_SPACE) != 0 ? " " : "");
+        final StringBuilder sb = new StringBuilder();
+        for (int index = 0; index < fields.length; index++) {
+            if (index > 0) {
+                sb.append(separator);
+            }
+            if (columnPositions != null && index < columnPositions.length) {
+                padToColumn(sb, columnPositions[index]);
+            }
+            final String escapedText = escapeField(fields[index], alwaysQuoted);
+            sb.append(escapedText);
+        }
+        return sb.toString();
+    }
+
+    @UsedForTesting
+    public static String join(final int joinFlags, final String... fields) {
+        return join(joinFlags, null, fields);
+    }
+
+    @UsedForTesting
+    public static String join(final String... fields) {
+        return join(JOIN_FLAGS_NONE, null, fields);
+    }
+}
diff --git a/java/src/com/android/inputmethod/dictionarypack/Utils.java b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
similarity index 76%
rename from java/src/com/android/inputmethod/dictionarypack/Utils.java
rename to java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
index c4a42db..c4ead0a 100644
--- a/java/src/com/android/inputmethod/dictionarypack/Utils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DebugLogUtils.java
@@ -14,16 +14,18 @@
  * the License.
  */
 
-package com.android.inputmethod.dictionarypack;
+package com.android.inputmethod.latin.utils;
 
 import android.util.Log;
 
+import com.android.inputmethod.latin.LatinImeLogger;
+
 /**
- * A class for various utility methods, especially debugging.
+ * A class for logging and debugging utility methods.
  */
-public final class Utils {
-    private final static String TAG = Utils.class.getSimpleName() + ":DEBUG --";
-    private final static boolean DEBUG = DictionaryProvider.DEBUG;
+public final class DebugLogUtils {
+    private final static String TAG = DebugLogUtils.class.getSimpleName();
+    private final static boolean sDBG = LatinImeLogger.sDBG;
 
     /**
      * Calls .toString() on its non-null argument or returns "null"
@@ -39,13 +41,22 @@
      * @return a readable, carriage-return-separated string for the current stack trace.
      */
     public static String getStackTrace() {
+        return getStackTrace(Integer.MAX_VALUE - 1);
+    }
+
+    /**
+     * Get the string representation of the current stack trace, for debugging purposes.
+     * @param limit the maximum number of stack frames to be returned.
+     * @return a readable, carriage-return-separated string for the current stack trace.
+     */
+    public static String getStackTrace(final int limit) {
         final StringBuilder sb = new StringBuilder();
         try {
             throw new RuntimeException();
-        } catch (RuntimeException e) {
-            StackTraceElement[] frames = e.getStackTrace();
+        } catch (final RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
             // Start at 1 because the first frame is here and we don't care about it
-            for (int j = 1; j < frames.length; ++j) {
+            for (int j = 1; j < frames.length && j < limit + 1; ++j) {
                 sb.append(frames[j].toString() + "\n");
             }
         }
@@ -75,7 +86,7 @@
      * @param args the stuff to send to the log
      */
     public static void l(final Object... args) {
-        if (!DEBUG) return;
+        if (!sDBG) return;
         final StringBuilder sb = new StringBuilder();
         for (final Object o : args) {
             sb.append(s(o).toString());
@@ -92,7 +103,7 @@
      * @param args the stuff to send to the log
      */
     public static void r(final Object... args) {
-        if (!DEBUG) return;
+        if (!sDBG) return;
         final StringBuilder sb = new StringBuilder("\u001B[31m");
         for (final Object o : args) {
             sb.append(s(o).toString());
diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
similarity index 95%
rename from java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
rename to java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index df7bad8..34eccd6 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -14,15 +14,17 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.text.format.DateUtils;
 import android.util.Log;
 
+import com.android.inputmethod.latin.AssetFileAddress;
+import com.android.inputmethod.latin.BinaryDictionaryGetter;
+import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtils;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
@@ -30,16 +32,16 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class encapsulates the logic for the Latin-IME side of dictionary information management.
  */
 public class DictionaryInfoUtils {
     private static final String TAG = DictionaryInfoUtils.class.getSimpleName();
-    // This class must be located in the same package as LatinIME.java.
-    private static final String RESOURCE_PACKAGE_NAME =
-            DictionaryInfoUtils.class.getPackage().getName();
+    private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName();
     private static final String DEFAULT_MAIN_DICT = "main";
     private static final String MAIN_DICT_PREFIX = "main_";
     // 6 digits - unicode is limited to 21 bits
@@ -72,8 +74,8 @@
             values.put(LOCALE_COLUMN, mLocale.toString());
             values.put(DESCRIPTION_COLUMN, mDescription);
             values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
-            values.put(DATE_COLUMN,
-                    new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+            values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
+                    new File(mFileAddress.mFilename).lastModified()));
             values.put(FILESIZE_COLUMN, mFileAddress.mLength);
             values.put(VERSION_COLUMN, mVersion);
             return values;
@@ -301,12 +303,14 @@
 
     private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
             final DictionaryInfo newElement) {
-        for (final DictionaryInfo info : dictList) {
-            if (info.mLocale.equals(newElement.mLocale)) {
-                if (newElement.mVersion <= info.mVersion) {
+        final Iterator<DictionaryInfo> iter = dictList.iterator();
+        while (iter.hasNext()) {
+            final DictionaryInfo thisDictInfo = iter.next();
+            if (thisDictInfo.mLocale.equals(newElement.mLocale)) {
+                if (newElement.mVersion <= thisDictInfo.mVersion) {
                     return;
                 }
-                dictList.remove(info);
+                iter.remove();
             }
         }
         dictList.add(newElement);
diff --git a/java/src/com/android/inputmethod/latin/FeedbackUtils.java b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java
similarity index 95%
rename from java/src/com/android/inputmethod/latin/FeedbackUtils.java
rename to java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java
index 0582763..ec7eaf4 100644
--- a/java/src/com/android/inputmethod/latin/FeedbackUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.Context;
 import android.content.Intent;
diff --git a/java/src/com/android/inputmethod/latin/FileTransforms.java b/java/src/com/android/inputmethod/latin/utils/FileTransforms.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/FileTransforms.java
rename to java/src/com/android/inputmethod/latin/utils/FileTransforms.java
index 692f3c7..9f4584e 100644
--- a/java/src/com/android/inputmethod/latin/FileTransforms.java
+++ b/java/src/com/android/inputmethod/latin/utils/FileTransforms.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/InputTypeUtils.java
rename to java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java
index 46194f6..19cd340 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.text.InputType;
 import android.view.inputmethod.EditorInfo;
diff --git a/java/src/com/android/inputmethod/latin/IntentUtils.java b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java
similarity index 97%
rename from java/src/com/android/inputmethod/latin/IntentUtils.java
rename to java/src/com/android/inputmethod/latin/utils/IntentUtils.java
index d175af5..ea01681 100644
--- a/java/src/com/android/inputmethod/latin/IntentUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.Intent;
 import android.text.TextUtils;
diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/utils/JniUtils.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/JniUtils.java
rename to java/src/com/android/inputmethod/latin/utils/JniUtils.java
index 8aedee5..e7fdafa 100644
--- a/java/src/com/android/inputmethod/latin/JniUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/JniUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.util.Log;
 
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
new file mode 100644
index 0000000..e958a7e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.utils;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.WordComposer;
+
+public final class LatinImeLoggerUtils {
+    private LatinImeLoggerUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void onNonSeparator(final char code, final int x, final int y) {
+        UserLogRingCharBuffer.getInstance().push(code, x, y);
+        LatinImeLogger.logOnInputChar();
+    }
+
+    public static void onSeparator(final int code, final int x, final int y) {
+        // Helper method to log a single code point separator
+        // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+        onSeparator(new String(new int[]{code}, 0, 1), x, y);
+    }
+
+    public static void onSeparator(final String separator, final int x, final int y) {
+        final int length = separator.length();
+        for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+            int codePoint = Character.codePointAt(separator, i);
+            // TODO: accept code points
+            UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
+        }
+        LatinImeLogger.logOnInputSeparator();
+    }
+
+    public static void onAutoCorrection(final String typedWord, final String correctedWord,
+            final String separatorString, final WordComposer wordComposer) {
+        final boolean isBatchMode = wordComposer.isBatchMode();
+        if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
+            return;
+        }
+        // TODO: this fails when the separator is more than 1 code point long, but
+        // the backend can't handle it yet. The only case when this happens is with
+        // smileys and other multi-character keys.
+        final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+                : separatorString.codePointAt(0);
+        if (!isBatchMode) {
+            LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
+        } else {
+            if (!TextUtils.isEmpty(correctedWord)) {
+                // We must make sure that InputPointer contains only the relative timestamps,
+                // not actual timestamps.
+                LatinImeLogger.logOnAutoCorrectionForGeometric(
+                        "", correctedWord, codePoint, wordComposer.getInputPointers());
+            }
+        }
+    }
+
+    public static void onAutoCorrectionCancellation() {
+        LatinImeLogger.logOnAutoCorrectionCancelled();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
similarity index 87%
rename from java/src/com/android/inputmethod/latin/LocaleUtils.java
rename to java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
index 5fde815..22045aa 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
-import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.text.TextUtils;
 
 import java.util.HashMap;
@@ -148,7 +146,7 @@
     public static String getMatchLevelSortedString(int matchLevel) {
         // This works because the match levels are 0~99 (actually 0~30)
         // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
-        return String.format("%02d", MATCH_LEVEL_MAX - matchLevel);
+        return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
     }
 
     /**
@@ -164,39 +162,6 @@
         return LOCALE_MATCH <= level;
     }
 
-    static final Object sLockForRunInLocale = new Object();
-
-    public abstract static class RunInLocale<T> {
-        protected abstract T job(Resources res);
-
-        /**
-         * Execute {@link #job(Resources)} method in specified system locale exclusively.
-         *
-         * @param res the resources to use. Pass current resources.
-         * @param newLocale the locale to change to
-         * @return the value returned from {@link #job(Resources)}.
-         */
-        public T runInLocale(final Resources res, final Locale newLocale) {
-            synchronized (sLockForRunInLocale) {
-                final Configuration conf = res.getConfiguration();
-                final Locale oldLocale = conf.locale;
-                final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
-                try {
-                    if (needsChange) {
-                        conf.locale = newLocale;
-                        res.updateConfiguration(conf, null);
-                    }
-                    return job(res);
-                } finally {
-                    if (needsChange) {
-                        conf.locale = oldLocale;
-                        res.updateConfiguration(conf, null);
-                    }
-                }
-            }
-        }
-    }
-
     private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap();
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java
similarity index 92%
rename from java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
rename to java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java
index a98ecc7..9ad319d 100644
--- a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java
+++ b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java
@@ -14,7 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
+
+import com.android.inputmethod.latin.R;
 
 import android.content.Context;
 
diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
similarity index 97%
rename from java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
rename to java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
index a880000..1fc7ecc 100644
--- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java
+++ b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.view.inputmethod.EditorInfo;
 
+import com.android.inputmethod.latin.RichInputConnection;
+
 import java.util.Locale;
 
 /**
diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
similarity index 95%
rename from java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
rename to java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
index 8a704ab..0f5cd80 100644
--- a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java
+++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java
@@ -14,9 +14,7 @@
  * the License.
  */
 
-package com.android.inputmethod.latin;
-
-import com.android.inputmethod.latin.StringUtils;
+package com.android.inputmethod.latin.utils;
 
 import java.util.Locale;
 
@@ -163,7 +161,10 @@
             final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd);
             if (!Character.isWhitespace(codePoint)) break;
         }
-        if (0 != nonWhitespaceStart || len != nonWhitespaceEnd) {
+        // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only
+        // whitespace, so we leave it as is.
+        if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd)
+                && nonWhitespaceStart < nonWhitespaceEnd) {
             mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd;
             mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart;
             mStringAfter = mStringBefore =
diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/ResizableIntArray.java
rename to java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
index 691f060..4c7739a 100644
--- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import java.util.Arrays;
 
diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
similarity index 75%
rename from java/src/com/android/inputmethod/latin/ResourceUtils.java
rename to java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
index a9fba53..ffec575 100644
--- a/java/src/com/android/inputmethod/latin/ResourceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -27,6 +27,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.regex.PatternSyntaxException;
 
 public final class ResourceUtils {
     private static final String TAG = ResourceUtils.class.getSimpleName();
@@ -83,22 +84,39 @@
             return overrideValue;
         }
 
-        final String defaultValue = findDefaultConstant(overrideArray);
-        // The defaultValue might be an empty string.
-        if (defaultValue == null) {
-            Log.w(TAG, "Couldn't find override value nor default value:"
-                    + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " build=" + sBuildKeyValuesDebugString);
-        } else {
-            Log.i(TAG, "Found default value:"
-                    + " resource="+ res.getResourceEntryName(overrideResId)
-                    + " build=" + sBuildKeyValuesDebugString
-                    + " default=" + defaultValue);
+        String defaultValue = null;
+        try {
+            defaultValue = findDefaultConstant(overrideArray);
+            // The defaultValue might be an empty string.
+            if (defaultValue == null) {
+                Log.w(TAG, "Couldn't find override value nor default value:"
+                        + " resource="+ res.getResourceEntryName(overrideResId)
+                        + " build=" + sBuildKeyValuesDebugString);
+            } else {
+                Log.i(TAG, "Found default value:"
+                        + " resource="+ res.getResourceEntryName(overrideResId)
+                        + " build=" + sBuildKeyValuesDebugString
+                        + " default=" + defaultValue);
+            }
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            Log.w(TAG, "Syntax error, ignored", e);
         }
         sDeviceOverrideValueMap.put(key, defaultValue);
         return defaultValue;
     }
 
+    @SuppressWarnings("serial")
+    static class DeviceOverridePatternSyntaxError extends Exception {
+        public DeviceOverridePatternSyntaxError(final String message, final String expression) {
+            this(message, expression, null);
+        }
+
+        public DeviceOverridePatternSyntaxError(final String message, final String expression,
+                final Throwable throwable) {
+            super(message + ": " + expression, throwable);
+        }
+    }
+
     /**
      * Find the condition that fulfills specified key value pairs from an array of
      * "condition,constant", and return the corresponding string constant. A condition is
@@ -123,10 +141,12 @@
         if (conditionConstantArray == null || keyValuePairs == null) {
             return null;
         }
+        String foundValue = null;
         for (final String conditionConstant : conditionConstantArray) {
             final int posComma = conditionConstant.indexOf(',');
             if (posComma < 0) {
-                throw new RuntimeException("Array element has no comma: " + conditionConstant);
+                Log.w(TAG, "Array element has no comma: " + conditionConstant);
+                continue;
             }
             final String condition = conditionConstant.substring(0, posComma);
             if (condition.isEmpty()) {
@@ -134,44 +154,59 @@
                 // {@link #findConstantForDefault(String[])}.
                 continue;
             }
-            if (fulfillsCondition(keyValuePairs, condition)) {
-                return conditionConstant.substring(posComma + 1);
+            try {
+                if (fulfillsCondition(keyValuePairs, condition)) {
+                    // Take first match
+                    if (foundValue == null) {
+                        foundValue = conditionConstant.substring(posComma + 1);
+                    }
+                    // And continue walking through all conditions.
+                }
+            } catch (final DeviceOverridePatternSyntaxError e) {
+                Log.w(TAG, "Syntax error, ignored", e);
             }
         }
-        return null;
+        return foundValue;
     }
 
     private static boolean fulfillsCondition(final HashMap<String,String> keyValuePairs,
-            final String condition) {
+            final String condition) throws DeviceOverridePatternSyntaxError {
         final String[] patterns = condition.split(":");
         // Check all patterns in a condition are true
+        boolean matchedAll = true;
         for (final String pattern : patterns) {
             final int posEqual = pattern.indexOf('=');
             if (posEqual < 0) {
-                throw new RuntimeException("Pattern has no '=': " + condition);
+                throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition);
             }
             final String key = pattern.substring(0, posEqual);
             final String value = keyValuePairs.get(key);
             if (value == null) {
-                throw new RuntimeException("Found unknown key: " + condition);
+                throw new DeviceOverridePatternSyntaxError("Unknown key", condition);
             }
             final String patternRegexpValue = pattern.substring(posEqual + 1);
-            if (!value.matches(patternRegexpValue)) {
-                return false;
+            try {
+                if (!value.matches(patternRegexpValue)) {
+                    matchedAll = false;
+                    // And continue walking through all patterns.
+                }
+            } catch (final PatternSyntaxException e) {
+                throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e);
             }
         }
-        return true;
+        return matchedAll;
     }
 
     @UsedForTesting
-    static String findDefaultConstant(final String[] conditionConstantArray) {
+    static String findDefaultConstant(final String[] conditionConstantArray)
+            throws DeviceOverridePatternSyntaxError {
         if (conditionConstantArray == null) {
             return null;
         }
         for (final String condition : conditionConstantArray) {
             final int posComma = condition.indexOf(',');
             if (posComma < 0) {
-                throw new RuntimeException("Array element has no comma: " + condition);
+                throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition);
             }
             if (posComma == 0) { // condition is empty.
                 return condition.substring(posComma + 1);
diff --git a/java/src/com/android/inputmethod/latin/utils/RunInLocale.java b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
new file mode 100644
index 0000000..2c9e3b1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/RunInLocale.java
@@ -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.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.res.Configuration;
+import android.content.res.Resources;
+
+import java.util.Locale;
+
+public abstract class RunInLocale<T> {
+    private static final Object sLockForRunInLocale = new Object();
+
+    protected abstract T job(final Resources res);
+
+    /**
+     * Execute {@link #job(Resources)} method in specified system locale exclusively.
+     *
+     * @param res the resources to use.
+     * @param newLocale the locale to change to.
+     * @return the value returned from {@link #job(Resources)}.
+     */
+    public T runInLocale(final Resources res, final Locale newLocale) {
+        synchronized (sLockForRunInLocale) {
+            final Configuration conf = res.getConfiguration();
+            final Locale oldLocale = conf.locale;
+            final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
+            try {
+                if (needsChange) {
+                    conf.locale = newLocale;
+                    res.updateConfiguration(conf, null);
+                }
+                return job(res);
+            } finally {
+                if (needsChange) {
+                    conf.locale = oldLocale;
+                    res.updateConfiguration(conf, null);
+                }
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
similarity index 96%
rename from java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
rename to java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
index e50af4d..44e5d17 100644
--- a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java
+++ b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.os.Handler;
 import android.os.Looper;
diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
similarity index 84%
rename from java/src/com/android/inputmethod/latin/StringUtils.java
rename to java/src/com/android/inputmethod/latin/utils/StringUtils.java
index ab050d7..7406d85 100644
--- a/java/src/com/android/inputmethod/latin/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -14,10 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.text.TextUtils;
 
+import com.android.inputmethod.latin.Constants;
+
 import java.util.ArrayList;
 import java.util.Locale;
 
@@ -35,33 +37,55 @@
         return text.codePointCount(0, text.length());
     }
 
-    public static boolean containsInArray(final String key, final String[] array) {
+    public static boolean containsInArray(final String text, final String[] array) {
         for (final String element : array) {
-            if (key.equals(element)) return true;
+            if (text.equals(element)) return true;
         }
         return false;
     }
 
-    public static boolean containsInCsv(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return false;
-        return containsInArray(key, csv.split(","));
+    /**
+     * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax.
+     * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain
+     * a comma character in it.
+     */
+    private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ",";
+
+    public static boolean containsInCommaSplittableText(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return false;
+        }
+        return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT));
     }
 
-    public static String appendToCsvIfNotExists(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return key;
-        if (containsInCsv(key, csv)) return csv;
-        return csv + "," + key;
+    public static String appendToCommaSplittableTextIfNotExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return text;
+        }
+        if (containsInCommaSplittableText(text, extraValues)) {
+            return extraValues;
+        }
+        return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text;
     }
 
-    public static String removeFromCsvIfExists(final String key, final String csv) {
-        if (TextUtils.isEmpty(csv)) return "";
-        final String[] elements = csv.split(",");
-        if (!containsInArray(key, elements)) return csv;
+    public static String removeFromCommaSplittableTextIfExists(final String text,
+            final String extraValues) {
+        if (TextUtils.isEmpty(extraValues)) {
+            return "";
+        }
+        final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT);
+        if (!containsInArray(text, elements)) {
+            return extraValues;
+        }
         final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1);
         for (final String element : elements) {
-            if (!key.equals(element)) result.add(element);
+            if (!text.equals(element)) {
+                result.add(element);
+            }
         }
-        return TextUtils.join(",", result);
+        return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result);
     }
 
     /**
@@ -131,44 +155,6 @@
         return codePoints;
     }
 
-    public static String[] parseCsvString(final String text) {
-        final int size = text.length();
-        if (size == 0) {
-            return null;
-        }
-        if (codePointCount(text) == 1) {
-            return text.codePointAt(0) == Constants.CSV_SEPARATOR ? null : new String[] { text };
-        }
-
-        ArrayList<String> list = null;
-        int start = 0;
-        for (int pos = 0; pos < size; pos++) {
-            final char c = text.charAt(pos);
-            if (c == Constants.CSV_SEPARATOR) {
-                // Skip empty entry.
-                if (pos - start > 0) {
-                    if (list == null) {
-                        list = CollectionUtils.newArrayList();
-                    }
-                    list.add(text.substring(start, pos));
-                }
-                // Skip comma
-                start = pos + 1;
-            } else if (c == Constants.CSV_ESCAPE) {
-                // Skip escape character and escaped character.
-                pos++;
-            }
-        }
-        final String remain = (size - start > 0) ? text.substring(start) : null;
-        if (list == null) {
-            return remain != null ? new String[] { remain } : null;
-        }
-        if (remain != null) {
-            list.add(remain);
-        }
-        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
diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
similarity index 83%
rename from java/src/com/android/inputmethod/latin/SubtypeLocale.java
rename to java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
index 4d88ecc..1672809 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java
+++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET;
 import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
@@ -25,13 +25,14 @@
 import android.util.Log;
 import android.view.inputmethod.InputMethodSubtype;
 
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
+import com.android.inputmethod.latin.DictionaryFactory;
+import com.android.inputmethod.latin.R;
 
 import java.util.HashMap;
 import java.util.Locale;
 
-public final class SubtypeLocale {
-    static final String TAG = SubtypeLocale.class.getSimpleName();
+public final class SubtypeLocaleUtils {
+    static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
     // This class must be located in the same package as LatinIME.java.
     private static final String RESOURCE_PACKAGE_NAME =
             DictionaryFactory.class.getPackage().getName();
@@ -69,7 +70,7 @@
     private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
             CollectionUtils.newHashMap();
 
-    private SubtypeLocale() {
+    private SubtypeLocaleUtils() {
         // Intentional empty constructor for utility class.
     }
 
@@ -217,9 +218,11 @@
         return getSubtypeDisplayNameInternal(subtype, displayLocale);
     }
 
-    public static String getSubtypeDisplayName(final InputMethodSubtype subtype) {
-        final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(subtype.getLocale());
-        return getSubtypeDisplayNameInternal(subtype, displayLocale);
+    public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
+        if (subtype == null) {
+            return "<null subtype>";
+        }
+        return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
     }
 
     private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
@@ -238,7 +241,7 @@
                             + " nameResId=" + subtype.getNameResId()
                             + " locale=" + subtype.getLocale()
                             + " extra=" + subtype.getExtraValue()
-                            + "\n" + Utils.getStackTrace());
+                            + "\n" + DebugLogUtils.getStackTrace());
                     return "";
                 }
             }
@@ -284,4 +287,46 @@
         }
         return keyboardLayoutSet;
     }
+
+    // InputMethodSubtype's display name for spacebar text in its locale.
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  | Short  Middle      Full
+    // ------ ------- - ---- --------- ----------------------
+    //  en_US qwerty  F  En  English   English (US)           exception
+    //  en_GB qwerty  F  En  English   English (UK)           exception
+    //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
+    //  fr    azerty  F  Fr  Français  Français
+    //  fr_CA qwerty  F  Fr  Français  Français (Canada)
+    //  de    qwertz  F  De  Deutsch   Deutsch
+    //  zz    qwerty  F      QWERTY    QWERTY
+    //  fr    qwertz  T  Fr  Français  Français
+    //  de    qwerty  T  De  Deutsch   Deutsch
+    //  en_US azerty  T  En  English   English (US)
+    //  zz    azerty  T      AZERTY    AZERTY
+
+    // Get InputMethodSubtype's full display name in its locale.
+    public static String getFullDisplayName(final InputMethodSubtype subtype) {
+        if (isNoLanguage(subtype)) {
+            return getKeyboardLayoutSetDisplayName(subtype);
+        }
+        return getSubtypeLocaleDisplayName(subtype.getLocale());
+    }
+
+    // Get InputMethodSubtype's middle display name in its locale.
+    public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
+        if (isNoLanguage(subtype)) {
+            return getKeyboardLayoutSetDisplayName(subtype);
+        }
+        final Locale locale = getSubtypeLocale(subtype);
+        return getSubtypeLocaleDisplayName(locale.getLanguage());
+    }
+
+    // Get InputMethodSubtype's short display name in its locale.
+    public static String getShortDisplayName(final InputMethodSubtype subtype) {
+        if (isNoLanguage(subtype)) {
+            return "";
+        }
+        final Locale locale = getSubtypeLocale(subtype);
+        return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
similarity index 97%
rename from java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java
rename to java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
index 947b0c5..afbe2ec 100644
--- a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java
+++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.Context;
 import android.content.pm.PackageInfo;
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
new file mode 100644
index 0000000..5793e41
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -0,0 +1,116 @@
+/*
+ * 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.utils;
+
+import android.text.Spanned;
+import android.text.style.SuggestionSpan;
+
+import java.util.Arrays;
+
+/**
+ * Represents a range of text, relative to the current cursor position.
+ */
+public final class TextRange {
+    private final CharSequence mTextAtCursor;
+    private final int mWordAtCursorStartIndex;
+    private final int mWordAtCursorEndIndex;
+    private final int mCursorIndex;
+
+    public final CharSequence mWord;
+
+    public int getNumberOfCharsInWordBeforeCursor() {
+        return mCursorIndex - mWordAtCursorStartIndex;
+    }
+
+    public int getNumberOfCharsInWordAfterCursor() {
+        return mWordAtCursorEndIndex - mCursorIndex;
+    }
+
+    /**
+     * Gets the suggestion spans that are put squarely on the word, with the exact start
+     * and end of the span matching the boundaries of the word.
+     * @return the list of spans.
+     */
+    public SuggestionSpan[] getSuggestionSpansAtWord() {
+        if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
+            return new SuggestionSpan[0];
+        }
+        final Spanned text = (Spanned)mTextAtCursor;
+        // Note: it's fine to pass indices negative or greater than the length of the string
+        // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
+        // spans were cut at the cursor position, and #getSpans(start, end) does not return
+        // spans that end at `start' or begin at `end'. Consider the following case:
+        //              this| is          (The | symbolizes the cursor position
+        //              ---- ---
+        // In this case, the cursor is in position 4, so the 0~7 span has been split into
+        // a 0~4 part and a 4~7 part.
+        // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
+        // of the span, and not the part from 4 to 7, so we would not realize the span actually
+        // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
+        // the 4~7 spans and we can merge them accordingly.
+        // Any span starting more than 1 char away from the word boundaries in any direction
+        // does not touch the word, so we don't need to consider it. That's why requesting
+        // -1 ~ +1 is enough.
+        // Of course this is only relevant if the cursor is at one end of the word. If it's
+        // in the middle, the -1 and +1 are not necessary, but they are harmless.
+        final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
+                mWordAtCursorEndIndex + 1, SuggestionSpan.class);
+        int readIndex = 0;
+        int writeIndex = 0;
+        for (; readIndex < spans.length; ++readIndex) {
+            final SuggestionSpan span = spans[readIndex];
+            // The span may be null, as we null them when we find duplicates. Cf a few lines
+            // down.
+            if (null == span) continue;
+            // Tentative span start and end. This may be modified later if we realize the
+            // same span is also applied to other parts of the string.
+            int spanStart = text.getSpanStart(span);
+            int spanEnd = text.getSpanEnd(span);
+            for (int i = readIndex + 1; i < spans.length; ++i) {
+                if (span.equals(spans[i])) {
+                    // We found the same span somewhere else. Read the new extent of this
+                    // span, and adjust our values accordingly.
+                    spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
+                    spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
+                    // ...and mark the span as processed.
+                    spans[i] = null;
+                }
+            }
+            if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
+                // If the span does not start and stop here, we ignore it. It probably extends
+                // past the start or end of the word, as happens in missing space correction
+                // or EasyEditSpans put by voice input.
+                spans[writeIndex++] = spans[readIndex];
+            }
+        }
+        return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
+    }
+
+    public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
+            final int wordAtCursorEndIndex, final int cursorIndex) {
+        if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
+                || cursorIndex > wordAtCursorEndIndex
+                || wordAtCursorEndIndex > textAtCursor.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        mTextAtCursor = textAtCursor;
+        mWordAtCursorStartIndex = wordAtCursorStartIndex;
+        mWordAtCursorEndIndex = wordAtCursorEndIndex;
+        mCursorIndex = cursorIndex;
+        mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
similarity index 97%
rename from java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
rename to java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index 6a54e11..544e4d2 100644
--- a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 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.
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
new file mode 100644
index 0000000..06826da
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2016 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.utils;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+import android.view.MotionEvent;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public final class UsabilityStudyLogUtils {
+    // TODO: remove code duplication with ResearchLog class
+    private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
+    private static final String FILENAME = "log.txt";
+    private final Handler mLoggingHandler;
+    private File mFile;
+    private File mDirectory;
+    private InputMethodService mIms;
+    private PrintWriter mWriter;
+    private final Date mDate;
+    private final SimpleDateFormat mDateFormat;
+
+    private UsabilityStudyLogUtils() {
+        mDate = new Date();
+        mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
+
+        HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        mLoggingHandler = new Handler(handlerThread.getLooper());
+    }
+
+    // Initialization-on-demand holder
+    private static final class OnDemandInitializationHolder {
+        public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
+    }
+
+    public static UsabilityStudyLogUtils getInstance() {
+        return OnDemandInitializationHolder.sInstance;
+    }
+
+    public void init(final InputMethodService ims) {
+        mIms = ims;
+        mDirectory = ims.getFilesDir();
+    }
+
+    private void createLogFileIfNotExist() {
+        if ((mFile == null || !mFile.exists())
+                && (mDirectory != null && mDirectory.exists())) {
+            try {
+                mWriter = getPrintWriter(mDirectory, FILENAME, false);
+            } catch (final IOException e) {
+                Log.e(USABILITY_TAG, "Can't create log file.");
+            }
+        }
+    }
+
+    public static void writeBackSpace(final int x, final int y) {
+        UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
+    }
+
+    public static void writeChar(final char c, final int x, final int y) {
+        String inputChar = String.valueOf(c);
+        switch (c) {
+            case '\n':
+                inputChar = "<enter>";
+                break;
+            case '\t':
+                inputChar = "<tab>";
+                break;
+            case ' ':
+                inputChar = "<space>";
+                break;
+        }
+        UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
+        LatinImeLogger.onPrintAllUsabilityStudyLogs();
+    }
+
+    public static void writeMotionEvent(final MotionEvent me) {
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        final int pointerCount = me.getPointerCount();
+        for (int index = 0; index < pointerCount; index++) {
+            final int id = me.getPointerId(index);
+            final int x = (int)me.getX(index);
+            final int y = (int)me.getY(index);
+            final float size = me.getSize(index);
+            final float pressure = me.getPressure(index);
+
+            final String eventTag;
+            switch (action) {
+            case MotionEvent.ACTION_UP:
+                eventTag = "[Up]";
+                break;
+            case MotionEvent.ACTION_DOWN:
+                eventTag = "[Down]";
+                break;
+            case MotionEvent.ACTION_POINTER_UP:
+                eventTag = "[PointerUp]";
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                eventTag = "[PointerDown]";
+                break;
+            case MotionEvent.ACTION_MOVE:
+                eventTag = "[Move]";
+                break;
+            default:
+                eventTag = "[Action" + action + "]";
+                break;
+            }
+            getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size
+                    + "," + pressure);
+        }
+    }
+
+    public void write(final String log) {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                createLogFileIfNotExist();
+                final long currentTime = System.currentTimeMillis();
+                mDate.setTime(currentTime);
+
+                final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
+                        mDateFormat.format(mDate), currentTime, log);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Write: " + log);
+                }
+                mWriter.print(printString);
+            }
+        });
+    }
+
+    private synchronized String getBufferedLogs() {
+        mWriter.flush();
+        final StringBuilder sb = new StringBuilder();
+        final BufferedReader br = getBufferedReader();
+        String line;
+        try {
+            while ((line = br.readLine()) != null) {
+                sb.append('\n');
+                sb.append(line);
+            }
+        } catch (final IOException e) {
+            Log.e(USABILITY_TAG, "Can't read log file.");
+        } finally {
+            if (LatinImeLogger.sDBG) {
+                Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+            }
+            try {
+                br.close();
+            } catch (final IOException e) {
+                // ignore.
+            }
+        }
+        return sb.toString();
+    }
+
+    public void emailResearcherLogsAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                final Date date = new Date();
+                date.setTime(System.currentTimeMillis());
+                final String currentDateTimeString =
+                        new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
+                if (mFile == null) {
+                    Log.w(USABILITY_TAG, "No internal log file found.");
+                    return;
+                }
+                if (mIms.checkCallingOrSelfPermission(
+                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                                    != PackageManager.PERMISSION_GRANTED) {
+                    Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+                    return;
+                }
+                mWriter.flush();
+                final String destPath = Environment.getExternalStorageDirectory()
+                        + "/research-" + currentDateTimeString + ".log";
+                final File destFile = new File(destPath);
+                try {
+                    final FileInputStream srcStream = new FileInputStream(mFile);
+                    final FileOutputStream destStream = new FileOutputStream(destFile);
+                    final FileChannel src = srcStream.getChannel();
+                    final FileChannel dest = destStream.getChannel();
+                    src.transferTo(0, src.size(), dest);
+                    src.close();
+                    srcStream.close();
+                    dest.close();
+                    destStream.close();
+                } catch (final FileNotFoundException e1) {
+                    Log.w(USABILITY_TAG, e1);
+                    return;
+                } catch (final IOException e2) {
+                    Log.w(USABILITY_TAG, e2);
+                    return;
+                }
+                if (!destFile.exists()) {
+                    Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+                    return;
+                }
+                final Intent intent = new Intent(Intent.ACTION_SEND);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+                }
+                intent.setType("text/plain");
+                intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+                intent.putExtra(Intent.EXTRA_SUBJECT,
+                        "[Research Logs] " + currentDateTimeString);
+                mIms.startActivity(intent);
+            }
+        });
+    }
+
+    public void printAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
+            }
+        });
+    }
+
+    public void clearAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mFile != null && mFile.exists()) {
+                    if (LatinImeLogger.sDBG) {
+                        Log.d(USABILITY_TAG, "Delete log file.");
+                    }
+                    mFile.delete();
+                    mWriter.close();
+                }
+            }
+        });
+    }
+
+    private BufferedReader getBufferedReader() {
+        createLogFileIfNotExist();
+        try {
+            return new BufferedReader(new FileReader(mFile));
+        } catch (final FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    private PrintWriter getPrintWriter(final File dir, final String filename,
+            final boolean renew) throws IOException {
+        mFile = new File(dir, filename);
+        if (mFile.exists()) {
+            if (renew) {
+                mFile.delete();
+            }
+        }
+        return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
similarity index 82%
rename from java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
rename to java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
index 1093155..d02f718 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.util.Log;
 
@@ -27,6 +27,7 @@
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.PendingAttribute;
 import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
 
 import java.io.IOException;
 import java.io.OutputStream;
@@ -53,64 +54,6 @@
         public int getFrequency(final String word1, final String word2);
     }
 
-    public static final class ByteArrayWrapper implements FusionDictionaryBufferInterface {
-        private byte[] mBuffer;
-        private int mPosition;
-
-        public ByteArrayWrapper(final byte[] buffer) {
-            mBuffer = buffer;
-            mPosition = 0;
-        }
-
-        @Override
-        public int readUnsignedByte() {
-            return mBuffer[mPosition++] & 0xFF;
-        }
-
-        @Override
-        public int readUnsignedShort() {
-            final int retval = readUnsignedByte();
-            return (retval << 8) + readUnsignedByte();
-        }
-
-        @Override
-        public int readUnsignedInt24() {
-            final int retval = readUnsignedShort();
-            return (retval << 8) + readUnsignedByte();
-        }
-
-        @Override
-        public int readInt() {
-            final int retval = readUnsignedShort();
-            return (retval << 16) + readUnsignedShort();
-        }
-
-        @Override
-        public int position() {
-            return mPosition;
-        }
-
-        @Override
-        public void position(int position) {
-            mPosition = position;
-        }
-
-        @Override
-        public void put(final byte b) {
-            mBuffer[mPosition++] = b;
-        }
-
-        @Override
-        public int limit() {
-            return mBuffer.length - 1;
-        }
-
-        @Override
-        public int capacity() {
-            return mBuffer.length;
-        }
-    }
-
     /**
      * Writes dictionary to file.
      */
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
similarity index 97%
rename from java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
rename to java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 9053d70..713a45b 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
-import android.text.format.DateUtils;
 import android.util.Log;
 
+import java.util.concurrent.TimeUnit;
+
 public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -27,8 +28,8 @@
     private static final int FC_LEVEL_MAX = 3;
     /* package */ static final int ELAPSED_TIME_MAX = 15;
     private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
-    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
-            * DateUtils.HOUR_IN_MILLIS;
+    private static final long ELAPSED_TIME_INTERVAL_MILLIS =
+            TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
     private static final int HALF_LIFE_HOURS = 48;
     private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
 
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
new file mode 100644
index 0000000..161386e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
@@ -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.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.inputmethodservice.InputMethodService;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.latin.settings.Settings;
+
+public final class UserLogRingCharBuffer {
+    public /* for test */ static final int BUFSIZE = 20;
+    public /* for test */ int mLength = 0;
+
+    private static UserLogRingCharBuffer sUserLogRingCharBuffer = new UserLogRingCharBuffer();
+    private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC';
+    private static final int INVALID_COORDINATE = -2;
+    private boolean mEnabled = false;
+    private int mEnd = 0;
+    private char[] mCharBuf = new char[BUFSIZE];
+    private int[] mXBuf = new int[BUFSIZE];
+    private int[] mYBuf = new int[BUFSIZE];
+
+    private UserLogRingCharBuffer() {
+        // Intentional empty constructor for singleton.
+    }
+
+    @UsedForTesting
+    public static UserLogRingCharBuffer getInstance() {
+        return sUserLogRingCharBuffer;
+    }
+
+    public static UserLogRingCharBuffer init(final InputMethodService context,
+            final boolean enabled, final boolean usabilityStudy) {
+        if (!(enabled || usabilityStudy)) {
+            return null;
+        }
+        sUserLogRingCharBuffer.mEnabled = true;
+        UsabilityStudyLogUtils.getInstance().init(context);
+        return sUserLogRingCharBuffer;
+    }
+
+    private static int normalize(final int in) {
+        int ret = in % BUFSIZE;
+        return ret < 0 ? ret + BUFSIZE : ret;
+    }
+
+    // TODO: accept code points
+    @UsedForTesting
+    public void push(final char c, final int x, final int y) {
+        if (!mEnabled) {
+            return;
+        }
+        mCharBuf[mEnd] = c;
+        mXBuf[mEnd] = x;
+        mYBuf[mEnd] = y;
+        mEnd = normalize(mEnd + 1);
+        if (mLength < BUFSIZE) {
+            ++mLength;
+        }
+    }
+
+    public char pop() {
+        if (mLength < 1) {
+            return PLACEHOLDER_DELIMITER_CHAR;
+        }
+        mEnd = normalize(mEnd - 1);
+        --mLength;
+        return mCharBuf[mEnd];
+    }
+
+    public char getBackwardNthChar(final int n) {
+        if (mLength <= n || n < 0) {
+            return PLACEHOLDER_DELIMITER_CHAR;
+        }
+        return mCharBuf[normalize(mEnd - n - 1)];
+    }
+
+    public int getPreviousX(final char c, final int back) {
+        final int index = normalize(mEnd - 2 - back);
+        if (mLength <= back
+                || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
+            return INVALID_COORDINATE;
+        }
+        return mXBuf[index];
+    }
+
+    public int getPreviousY(final char c, final int back) {
+        int index = normalize(mEnd - 2 - back);
+        if (mLength <= back
+                || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) {
+            return INVALID_COORDINATE;
+        }
+        return mYBuf[index];
+    }
+
+    public String getLastWord(final int ignoreCharCount) {
+        final StringBuilder sb = new StringBuilder();
+        int i = ignoreCharCount;
+        for (; i < mLength; ++i) {
+            final char c = mCharBuf[normalize(mEnd - 1 - i)];
+            if (!Settings.getInstance().isWordSeparator(c)) {
+                break;
+            }
+        }
+        for (; i < mLength; ++i) {
+            char c = mCharBuf[normalize(mEnd - 1 - i)];
+            if (!Settings.getInstance().isWordSeparator(c)) {
+                sb.append(c);
+            } else {
+                break;
+            }
+        }
+        return sb.reverse().toString();
+    }
+
+    public void reset() {
+        mLength = 0;
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
similarity index 86%
rename from java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
rename to java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dc12fa4..f9d8534 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -27,7 +27,8 @@
         // This utility class is not publicly instantiable.
     }
 
-    public static MarginLayoutParams newLayoutParam(ViewGroup placer, int width, int height) {
+    public static MarginLayoutParams newLayoutParam(final ViewGroup placer, final int width,
+            final int height) {
         if (placer instanceof FrameLayout) {
             return new FrameLayout.LayoutParams(width, height);
         } else if (placer instanceof RelativeLayout) {
@@ -40,7 +41,8 @@
         }
     }
 
-    public static void placeViewAt(View view, int x, int y, int w, int h) {
+    public static void placeViewAt(final View view, final int x, final int y, final int w,
+            final int h) {
         final ViewGroup.LayoutParams lp = view.getLayoutParams();
         if (lp instanceof MarginLayoutParams) {
             final MarginLayoutParams marginLayoutParams = (MarginLayoutParams)lp;
diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java
similarity index 98%
rename from java/src/com/android/inputmethod/latin/XmlParseUtils.java
rename to java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java
index 48e5ed3..bdad166 100644
--- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.res.TypedArray;
 
diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java
index b985fda..520b88d 100644
--- a/java/src/com/android/inputmethod/research/FeedbackActivity.java
+++ b/java/src/com/android/inputmethod/research/FeedbackActivity.java
@@ -18,7 +18,6 @@
 
 import android.app.Activity;
 import android.os.Bundle;
-import android.widget.CheckBox;
 
 import com.android.inputmethod.latin.R;
 
diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java
index a073829..75fbbf1 100644
--- a/java/src/com/android/inputmethod/research/FeedbackFragment.java
+++ b/java/src/com/android/inputmethod/research/FeedbackFragment.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.research;
 
-import android.app.Activity;
 import android.app.Fragment;
 import android.os.Bundle;
 import android.text.Editable;
diff --git a/java/src/com/android/inputmethod/latin/FeedbackUtils.java b/java/src/com/android/inputmethod/research/FeedbackLog.java
similarity index 62%
copy from java/src/com/android/inputmethod/latin/FeedbackUtils.java
copy to java/src/com/android/inputmethod/research/FeedbackLog.java
index 0582763..5af194c 100644
--- a/java/src/com/android/inputmethod/latin/FeedbackUtils.java
+++ b/java/src/com/android/inputmethod/research/FeedbackLog.java
@@ -14,24 +14,19 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.research;
 
 import android.content.Context;
-import android.content.Intent;
 
-public class FeedbackUtils {
-    public static boolean isFeedbackFormSupported() {
-        return false;
+import java.io.File;
+
+public class FeedbackLog extends ResearchLog {
+    public FeedbackLog(final File outputFile, final Context context) {
+        super(outputFile, context);
     }
 
-    public static void showFeedbackForm(Context context) {
-    }
-
-    public static int getAboutKeyboardTitleResId() {
-        return 0;
-    }
-
-    public static Intent getAboutKeyboardIntent(Context context) {
-        return null;
+    @Override
+    public boolean isFeedbackLog() {
+        return true;
     }
 }
diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java
index 24cd8d9..63d524d 100644
--- a/java/src/com/android/inputmethod/research/JsonUtils.java
+++ b/java/src/com/android/inputmethod/research/JsonUtils.java
@@ -94,12 +94,17 @@
                 .value(words.mIsPunctuationSuggestions);
         jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions);
         jsonWriter.name("isPrediction").value(words.mIsPrediction);
-        jsonWriter.name("words");
+        jsonWriter.name("suggestedWords");
         jsonWriter.beginArray();
         final int size = words.size();
         for (int j = 0; j < size; j++) {
             final SuggestedWordInfo wordInfo = words.getInfo(j);
-            jsonWriter.value(wordInfo.toString());
+            jsonWriter.beginObject();
+            jsonWriter.name("word").value(wordInfo.toString());
+            jsonWriter.name("score").value(wordInfo.mScore);
+            jsonWriter.name("kind").value(wordInfo.mKind);
+            jsonWriter.name("sourceDict").value(wordInfo.mSourceDict);
+            jsonWriter.endObject();
         }
         jsonWriter.endArray();
         jsonWriter.endObject();
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index cf1388f..3366df1 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -67,7 +67,7 @@
     private String[] mWordArray = EMPTY_STRING_ARRAY;
     private boolean mMayContainDigit;
     private boolean mIsPartOfMegaword;
-    private boolean mContainsCorrection;
+    private boolean mContainsUserDeletions;
 
     // mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the
     // correction.
@@ -146,7 +146,8 @@
         if (size != 0) {
             // Note that jsonWriter is only set to a non-null value if the logUnit start text is
             // output and at least one logStatement is output.
-            JsonWriter jsonWriter = null;
+            JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked();
+            outputLogUnitStart(jsonWriter, canIncludePrivateData);
             for (int i = 0; i < size; i++) {
                 final LogStatement logStatement = mLogStatementList.get(i);
                 if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) {
@@ -155,42 +156,35 @@
                 if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) {
                     continue;
                 }
-                // Only retrieve the jsonWriter if we need to.  If we don't get this far, then
-                // researchLog.getInitializedJsonWriterLocked() will not ever be called, and the
-                // file will not have been opened for writing.
-                if (jsonWriter == null) {
-                    jsonWriter = researchLog.getInitializedJsonWriterLocked();
-                    outputLogUnitStart(jsonWriter, canIncludePrivateData);
-                }
                 logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
             }
-            if (jsonWriter != null) {
-                // We must have called logUnitStart earlier, so emit a logUnitStop.
-                outputLogUnitStop(jsonWriter);
-            }
+            outputLogUnitStop(jsonWriter);
         }
     }
 
     private static final String WORD_KEY = "_wo";
+    private static final String NUM_WORDS_KEY = "_nw";
     private static final String CORRECTION_TYPE_KEY = "_corType";
     private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart";
     private static final String LOG_UNIT_END_KEY = "logUnitEnd";
 
     final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA =
             new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
-                    false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY);
+                    false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY,
+                    NUM_WORDS_KEY);
     final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA =
             new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */,
-                    false /* isPotentiallyRevealing */);
+                    false /* isPotentiallyRevealing */, NUM_WORDS_KEY);
     private void outputLogUnitStart(final JsonWriter jsonWriter,
             final boolean canIncludePrivateData) {
         final LogStatement logStatement;
         if (canIncludePrivateData) {
             LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter,
-                    SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType());
+                    SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(),
+                    getNumWords());
         } else {
             LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter,
-                    SystemClock.uptimeMillis());
+                    SystemClock.uptimeMillis(), getNumWords());
         }
     }
 
@@ -277,13 +271,13 @@
     }
 
     // TODO: Refactor to eliminate getter/setters
-    public void setContainsCorrection() {
-        mContainsCorrection = true;
+    public void setContainsUserDeletions() {
+        mContainsUserDeletions = true;
     }
 
     // TODO: Refactor to eliminate getter/setters
-    public boolean containsCorrection() {
-        return mContainsCorrection;
+    public boolean containsUserDeletions() {
+        return mContainsUserDeletions;
     }
 
     // TODO: Refactor to eliminate getter/setters
@@ -323,7 +317,7 @@
                         true /* isPartOfMegaword */);
                 newLogUnit.mWords = null;
                 newLogUnit.mMayContainDigit = mMayContainDigit;
-                newLogUnit.mContainsCorrection = mContainsCorrection;
+                newLogUnit.mContainsUserDeletions = mContainsUserDeletions;
 
                 // Purge the logStatements and associated data from this LogUnit.
                 laterLogStatements.clear();
@@ -346,7 +340,7 @@
             setWords(logUnit.mWords);
         }
         mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit;
-        mContainsCorrection = mContainsCorrection || logUnit.mContainsCorrection;
+        mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions;
         mIsPartOfMegaword = false;
     }
 
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 9aa3499..6df7c17 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -63,6 +63,15 @@
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
 
+    // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode()
+    public static final int PUBLISHABILITY_PUBLISHABLE = 0;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5;
+    public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6;
+
     // The size of the n-grams logged.  E.g. N_GRAM_SIZE = 2 means to sample bigrams.
     public static final int N_GRAM_SIZE = 2;
 
@@ -105,21 +114,24 @@
     }
 
     /**
-     * Determines whether uploading the n words at the front the MainLogBuffer will not violate
-     * user privacy.
+     * Determines whether the string determined by a series of LogUnits will not violate user
+     * privacy if published.
      *
-     * The size of the MainLogBuffer is just enough to hold one n-gram, its corrections, and any
-     * non-character data that is typed between words.  The decision about privacy is made based on
-     * the buffer's entire content.  If it is decided that the privacy risks are too great to upload
-     * the contents of this buffer, a censored version of the LogItems may still be uploaded.  E.g.,
-     * the screen orientation and other characteristics about the device can be uploaded without
-     * revealing much about the user.
+     * @param logUnits a LogUnit list to check for publishability
+     * @param nGramSize the smallest n-gram acceptable to be published.  if
+     * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than
+     * {@code minNGramSize} words in the logUnits, otherwise wait.  if {@link
+     * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize
+     * words in the LogUnits.
+     *
+     * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class.
      */
-    private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) {
+    private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits,
+            final int nGramSize) {
         // Bypass privacy checks when debugging.
         if (ResearchLogger.IS_LOGGING_EVERYTHING) {
             if (mIsStopping) {
-                return true;
+                return PUBLISHABILITY_UNPUBLISHABLE_STOPPING;
             }
             // Only check that it is the right length.  If not, wait for later words to make
             // complete n-grams.
@@ -129,13 +141,17 @@
                 final LogUnit logUnit = logUnits.get(i);
                 numWordsInLogUnitList += logUnit.getNumWords();
             }
-            return numWordsInLogUnitList >= minNGramSize;
+            if (numWordsInLogUnitList >= nGramSize) {
+                return PUBLISHABILITY_PUBLISHABLE;
+            } else {
+                return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+            }
         }
 
         // Check that we are not sampling too frequently.  Having sampled recently might disclose
         // too much of the user's intended meaning.
         if (mNumWordsUntilSafeToSample > 0) {
-            return false;
+            return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY;
         }
         // Reload the dictionary in case it has changed (e.g., because the user has changed
         // languages).
@@ -144,7 +160,7 @@
             // Main dictionary is unavailable.  Since we cannot check it, we cannot tell if a
             // word is out-of-vocabulary or not.  Therefore, we must judge the entire buffer
             // contents to potentially pose a privacy risk.
-            return false;
+            return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE;
         }
 
         // Check each word in the buffer.  If any word poses a privacy threat, we cannot upload
@@ -155,7 +171,7 @@
             if (!logUnit.hasOneOrMoreWords()) {
                 // Digits outside words are a privacy threat.
                 if (logUnit.mayContainDigit()) {
-                    return false;
+                    return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT;
                 }
             } else {
                 numWordsInLogUnitList += logUnit.getNumWords();
@@ -168,14 +184,18 @@
                                     + ResearchLogger.hasLetters(word)
                                     + ", isValid: " + (dictionary.isValidWord(word)));
                         }
-                        return false;
+                        return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY;
                     }
                 }
             }
         }
 
         // Finally, only return true if the ngram is the right size.
-        return numWordsInLogUnitList == minNGramSize;
+        if (numWordsInLogUnitList == nGramSize) {
+            return PUBLISHABILITY_PUBLISHABLE;
+        } else {
+            return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT;
+        }
     }
 
     public void shiftAndPublishAll() throws IOException {
@@ -196,11 +216,29 @@
         }
     }
 
+    /**
+     * If there is a safe n-gram at the front of this log buffer, publish it with all details, and
+     * remove the LogUnits that constitute it.
+     *
+     * An n-gram might not be "safe" if it violates privacy controls.  E.g., it might contain
+     * numbers, an out-of-vocabulary word, or another n-gram may have been published recently.  If
+     * there is no safe n-gram, then the LogUnits up through the first word-containing LogUnit are
+     * published, but without disclosing any privacy-related details, such as the word the LogUnit
+     * generated, motion data, etc.
+     *
+     * Note that a LogUnit can hold more than one word if the user types without explicit spaces.
+     * In this case, the words may be grouped together in such a way that pulling an n-gram off the
+     * front would require splitting a LogUnit.  Splitting a LogUnit is not possible, so this case
+     * is treated just as the unsafe n-gram case.  This may cause n-grams to be sampled at slightly
+     * less than the target frequency.
+     */
     protected final void publishLogUnitsAtFrontOfBuffer() throws IOException {
         // TODO: Refactor this method to require fewer passes through the LogUnits.  Should really
         // require only one pass.
         ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE);
-        if (isSafeNGram(logUnits, N_GRAM_SIZE)) {
+        final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE);
+        ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode);
+        if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) {
             // Good n-gram at the front of the buffer.  Publish it, disclosing details.
             publish(logUnits, true /* canIncludePrivateData */);
             shiftOutWords(N_GRAM_SIZE);
diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java
index fbfd9b5..3388645 100644
--- a/java/src/com/android/inputmethod/research/MotionEventReader.java
+++ b/java/src/com/android/inputmethod/research/MotionEventReader.java
@@ -315,16 +315,6 @@
         return pointerCoords;
     }
 
-    /**
-     * Tests that {@code x} is uninitialized.
-     *
-     * Assumes that {@code x} will never be given a valid value less than 0, and that
-     * UNINITIALIZED_FLOAT is less than 0.0f.
-     */
-    private boolean isUninitializedFloat(final float x) {
-        return x < 0.0f;
-    }
-
     private void addMotionEventData(final ReplayData replayData, final int actionType,
             final long time, final PointerProperties[] pointerProperties,
             final PointerCoords[] pointerCoords) {
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 3e82139..46e620a 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -27,7 +27,6 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
@@ -56,7 +55,7 @@
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-    private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+    private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
 
     /* package */ final ScheduledExecutorService mExecutor;
     /* package */ final File mFile;
@@ -81,6 +80,17 @@
     }
 
     /**
+     * Returns true if this is a FeedbackLog.
+     *
+     * FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal
+     * logging, they contain a LogStatement with the complete feedback string and optionally a
+     * recording of the user's supplied demo of the problem.
+     */
+    public boolean isFeedbackLog() {
+        return false;
+    }
+
+    /**
      * Waits for any publication requests to finish and closes the {@link JsonWriter} used for
      * output.
      *
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 8b8ea21..25187ced 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -20,11 +20,7 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.app.AlertDialog;
-import android.app.Dialog;
 import android.content.Context;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.PackageInfo;
@@ -34,7 +30,6 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.Paint.Style;
-import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -42,12 +37,9 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.Window;
-import android.view.WindowManager;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -61,15 +53,16 @@
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
-import com.android.inputmethod.latin.InputTypeUtils;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.MotionEventReader.ReplayData;
+import com.android.inputmethod.research.ui.SplashScreen;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -81,8 +74,11 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
+// TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls
+// enqueueEvent to record a LogStatement).
 /**
  * Logs the use of the LatinIME keyboard.
  *
@@ -92,12 +88,12 @@
  * This functionality is off by default. See
  * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}.
  */
-public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener {
+public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener,
+        SplashScreen.UserConsentListener {
     // TODO: This class has grown quite large and combines several concerns that should be
     // separated.  The following refactorings will be applied as soon as possible after adding
     // support for replaying historical events, fixing some replay bugs, adding some ui constraints
     // on the feedback dialog, and adding the survey dialog.
-    // TODO: Refactor.  Move splash screen code into separate class.
     // TODO: Refactor.  Move feedback screen code into separate class.
     // TODO: Refactor.  Move logging invocations into their own class.
     // TODO: Refactor.  Move currentLogUnit management into separate class.
@@ -141,10 +137,10 @@
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
     private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
-    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
-    private static final long MAX_LOGFILE_AGE_IN_MS = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1);
+    private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4);
 
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
@@ -182,8 +178,8 @@
     private final MotionEventReader mMotionEventReader = new MotionEventReader();
     private final Replayer mReplayer = Replayer.getInstance();
     private ResearchLogDirectory mResearchLogDirectory;
+    private SplashScreen mSplashScreen;
 
-    private Intent mUploadIntent;
     private Intent mUploadNowIntent;
 
     /* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
@@ -194,9 +190,16 @@
     // gesture, and when committing the earlier word, split the LogUnit.
     private long mSavedDownEventTime;
     private Bundle mFeedbackDialogBundle = null;
+    // Whether the feedback dialog is visible, and the user is typing into it.  Normal logging is
+    // not performed on text that the user types into the feedback dialog.
     private boolean mInFeedbackDialog = false;
     private Handler mUserRecordingTimeoutHandler;
-    private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS;
+    private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
+
+    // Stores a temporary LogUnit while generating a phantom space.  Needed because phantom spaces
+    // are issued out-of-order, immediately before the characters generated by other operations that
+    // have already outputted LogStatements.
+    private LogUnit mPhantomSpaceLogUnit = null;
 
     private ResearchLogger() {
         mStatistics = Statistics.getInstance();
@@ -229,7 +232,6 @@
         resetLogBuffers();
 
         // Initialize external services
-        mUploadIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -253,14 +255,14 @@
                     if (DEBUG) {
                         final String wordsString = logUnit.getWordsAsString();
                         Log.d(TAG, "onPublish: '" + wordsString
-                                + "', hc: " + logUnit.containsCorrection()
+                                + "', hc: " + logUnit.containsUserDeletions()
                                 + ", cipd: " + canIncludePrivateData);
                     }
                     for (final String word : logUnit.getWordsAsStringArray()) {
                         final Dictionary dictionary = getDictionary();
                         mStatistics.recordWordEntered(
                                 dictionary != null && dictionary.isValidWord(word),
-                                logUnit.containsCorrection());
+                                logUnit.containsUserDeletions());
                     }
                 }
                 publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData);
@@ -292,62 +294,19 @@
         }
     }
 
-    private Dialog mSplashDialog = null;
-
     private void maybeShowSplashScreen() {
-        if (ResearchSettings.readHasSeenSplash(mPrefs)) {
-            return;
-        }
-        if (mSplashDialog != null && mSplashDialog.isShowing()) {
-            return;
-        }
-        final IBinder windowToken = mMainKeyboardView != null
-                ? mMainKeyboardView.getWindowToken() : null;
-        if (windowToken == null) {
-            return;
-        }
-        final AlertDialog.Builder builder = new AlertDialog.Builder(mLatinIME)
-                .setTitle(R.string.research_splash_title)
-                .setMessage(R.string.research_splash_content)
-                .setPositiveButton(android.R.string.yes,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                onUserLoggingConsent();
-                                mSplashDialog.dismiss();
-                            }
-                })
-                .setNegativeButton(android.R.string.no,
-                        new DialogInterface.OnClickListener() {
-                            @Override
-                            public void onClick(DialogInterface dialog, int which) {
-                                final String packageName = mLatinIME.getPackageName();
-                                final Uri packageUri = Uri.parse("package:" + packageName);
-                                final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
-                                        packageUri);
-                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                                mLatinIME.startActivity(intent);
-                            }
-                })
-                .setCancelable(true)
-                .setOnCancelListener(
-                        new OnCancelListener() {
-                            @Override
-                            public void onCancel(DialogInterface dialog) {
-                                mLatinIME.requestHideSelf(0);
-                            }
-                });
-        mSplashDialog = builder.create();
-        final Window w = mSplashDialog.getWindow();
-        final WindowManager.LayoutParams lp = w.getAttributes();
-        lp.token = windowToken;
-        lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-        w.setAttributes(lp);
-        w.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
-        mSplashDialog.show();
+        if (ResearchSettings.readHasSeenSplash(mPrefs)) return;
+        if (mSplashScreen != null && mSplashScreen.isShowing()) return;
+        if (mMainKeyboardView == null) return;
+        final IBinder windowToken = mMainKeyboardView.getWindowToken();
+        if (windowToken == null) return;
+
+        mSplashScreen = new SplashScreen(mLatinIME, this);
+        mSplashScreen.showSplashScreen(windowToken);
     }
 
-    public void onUserLoggingConsent() {
+    @Override
+    public void onSplashScreenUserClickedOk() {
         if (mPrefs == null) {
             mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
             if (mPrefs == null) return;
@@ -358,12 +317,6 @@
         restart();
     }
 
-    private void setLoggingAllowed(final boolean enableLogging) {
-        if (mPrefs == null) return;
-        sIsLogging = enableLogging;
-        ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, enableLogging);
-    }
-
     private void checkForEmptyEditor() {
         if (mLatinIME == null) {
             return;
@@ -420,6 +373,7 @@
         mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
 
         resetLogBuffers();
+        cancelFeedbackDialog();
     }
 
     public void abort() {
@@ -456,6 +410,12 @@
         presentFeedbackDialog(latinIME);
     }
 
+    public void presentFeedbackDialogFromSettings() {
+        if (mLatinIME != null) {
+            presentFeedbackDialog(mLatinIME);
+        }
+    }
+
     public void presentFeedbackDialog(final LatinIME latinIME) {
         if (isMakingUserRecording()) {
             saveRecording();
@@ -574,8 +534,8 @@
             toast.show();
             boolean isLogDeleted = abort();
             final long currentTime = System.currentTimeMillis();
-            final long resumeTime = currentTime + 1000 * 60 *
-                    SUSPEND_DURATION_IN_MINUTES;
+            final long resumeTime = currentTime
+                    + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
             suspendLoggingUntil(resumeTime);
             toast.cancel();
             Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
@@ -650,7 +610,7 @@
         feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
                 feedbackContents, accountName, recording);
 
-        final ResearchLog feedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
+        final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath(
                 System.currentTimeMillis(), System.nanoTime()), mLatinIME);
         final LogBuffer feedbackLogBuffer = new LogBuffer();
         feedbackLogBuffer.shiftIn(feedbackLogUnit);
@@ -667,7 +627,7 @@
                             mMotionEventReader.readMotionEventData(mUserRecordingFile);
                     mReplayer.replay(replayData, null);
                 }
-            }, 1000);
+            }, TimeUnit.SECONDS.toMillis(1));
         }
 
         if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
@@ -692,13 +652,19 @@
         mInFeedbackDialog = false;
     }
 
+    private void cancelFeedbackDialog() {
+        if (isMakingUserRecording()) {
+            cancelRecording();
+        }
+        mInFeedbackDialog = false;
+    }
+
     public void initSuggest(final Suggest suggest) {
         mSuggest = suggest;
         // MainLogBuffer now has an out-of-date Suggest object.  Close down MainLogBuffer and create
         // a new one.
         if (mMainLogBuffer != null) {
-            stop();
-            start();
+            restart();
         }
     }
 
@@ -713,8 +679,28 @@
         mIsPasswordView = isPasswordView;
     }
 
-    private boolean isAllowedToLog() {
-        return !mIsPasswordView && sIsLogging && !mInFeedbackDialog;
+    /**
+     * Returns true if logging is permitted.
+     *
+     * This method is called when adding a LogStatement to a LogUnit, and when adding a LogUnit to a
+     * ResearchLog.  It is checked in both places in case conditions change between these times, and
+     * as a defensive measure in case refactoring changes the logging pipeline.
+     */
+    private boolean isAllowedToLogTo(final ResearchLog researchLog) {
+        // Logging is never allowed in these circumstances
+        if (mIsPasswordView) return false;
+        if (!sIsLogging) return false;
+        if (mInFeedbackDialog) {
+            // The FeedbackDialog is up.  Normal logging should not happen (the user might be trying
+            // out things while the dialog is up, and their reporting of an issue may not be
+            // representative of what they normally type).  However, after the user has finished
+            // entering their feedback, the logger packs their comments and an encoded version of
+            // any demonstration of the issue into a special "FeedbackLog".  So if the FeedbackLog
+            // is the destination, we do want to allow logging to it.
+            return researchLog.isFeedbackLog();
+        }
+        // No other exclusions.  Logging is permitted.
+        return true;
     }
 
     public void requestIndicatorRedraw() {
@@ -747,7 +733,7 @@
         // and remove this method.
         // The check for MainKeyboardView ensures that the indicator only decorates the main
         // keyboard, not every keyboard.
-        if (IS_SHOWING_INDICATOR && (isAllowedToLog() || isReplaying())
+        if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying())
                 && view instanceof MainKeyboardView) {
             final int savedColor = paint.getColor();
             paint.setColor(getIndicatorColor());
@@ -782,7 +768,7 @@
     private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
             final Object... values) {
         assert values.length == logStatement.getKeys().length;
-        if (isAllowedToLog() && logUnit != null) {
+        if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) {
             final long time = SystemClock.uptimeMillis();
             logUnit.addLogStatement(logStatement, time, values);
         }
@@ -792,8 +778,8 @@
         mCurrentLogUnit.setMayContainDigit();
     }
 
-    private void setCurrentLogUnitContainsCorrection() {
-        mCurrentLogUnit.setContainsCorrection();
+    private void setCurrentLogUnitContainsUserDeletions() {
+        mCurrentLogUnit.setContainsUserDeletions();
     }
 
     private void setCurrentLogUnitCorrectionType(final int correctionType) {
@@ -825,20 +811,22 @@
         // The user has deleted this word and returned to the previous.  Check that the word in the
         // logUnit matches the expected word.  If so, restore the last log unit committed to be the
         // current logUnit.  I.e., pull out the last LogUnit from all the LogBuffers, and make
-        // restore it to mCurrentLogUnit so the new edits are captured with the word.  Optionally
-        // dump the contents of mCurrentLogUnit (useful if they contain deletions of the next word
-        // that should not be reported to protect user privacy)
+        // it the mCurrentLogUnit so the new edits are captured with the word.  Optionally dump the
+        // contents of mCurrentLogUnit (useful if they contain deletions of the next word that
+        // should not be reported to protect user privacy)
         //
         // Note that we don't use mLastLogUnit here, because it only goes one word back and is only
         // needed for reverts, which only happen one back.
         final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit();
 
-        // Check that expected word matches.
+        // Check that expected word matches.  It's ok if both strings are null, because this is the
+        // case where the LogUnit is storing a non-word, e.g. a separator.
         if (oldLogUnit != null) {
+            // Because the word is stored in the LogUnit with digits scrubbed, the comparison must
+            // be made on a scrubbed version of the expectedWord as well.
+            final String scrubbedExpectedWord = scrubDigitsFromString(expectedWord);
             final String oldLogUnitWords = oldLogUnit.getWordsAsString();
-            if (oldLogUnitWords != null && !oldLogUnitWords.equals(expectedWord)) {
-                return;
-            }
+            if (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return;
         }
 
         // Uncommit, merging if necessary.
@@ -881,7 +869,7 @@
             final ResearchLog researchLog, final boolean canIncludePrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
         if (logUnits.isEmpty()) return;
-        if (!isAllowedToLog()) return;
+        if (!isAllowedToLogTo(researchLog)) return;
         // LogUnits not containing private data, such as contextual data for the log, do not require
         // logSegment boundary statements.
         if (canIncludePrivateData) {
@@ -893,7 +881,7 @@
             if (DEBUG) {
                 Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords()
                         ? logUnit.getWordsAsString() : "<wordless>")
-                        + ", correction?: " + logUnit.containsCorrection());
+                        + ", correction?: " + logUnit.containsUserDeletions());
             }
             researchLog.publish(logUnit, canIncludePrivateData);
         }
@@ -954,7 +942,8 @@
         return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint;
     }
 
-    /* package for test */ static String scrubDigitsFromString(String s) {
+    /* package for test */ static String scrubDigitsFromString(final String s) {
+        if (s == null) return null;
         StringBuilder sb = null;
         final int length = s.length();
         for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) {
@@ -1077,22 +1066,24 @@
     private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT =
             new LogStatement("MotionEvent", true, false, "action",
                     LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent");
-    public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
-            final long eventTime, final int index, final int id, final int x, final int y) {
-        if (me != null) {
-            final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
-            final ResearchLogger researchLogger = getInstance();
-            researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
-                    actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
-            if (action == MotionEvent.ACTION_DOWN) {
-                // Subtract 1 from eventTime so the down event is included in the later
-                // LogUnit, not the earlier (the test is for inequality).
-                researchLogger.setSavedDownEventTime(eventTime - 1);
-            }
-            // Refresh the timer in case we are capturing user feedback.
-            if (researchLogger.isMakingUserRecording()) {
-                researchLogger.resetRecordingTimer();
-            }
+    public static void mainKeyboardView_processMotionEvent(final MotionEvent me) {
+        if (me == null) {
+            return;
+        }
+        final int action = me.getActionMasked();
+        final long eventTime = me.getEventTime();
+        final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
+                actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
+        if (action == MotionEvent.ACTION_DOWN) {
+            // Subtract 1 from eventTime so the down event is included in the later
+            // LogUnit, not the earlier (the test is for inequality).
+            researchLogger.setSavedDownEventTime(eventTime - 1);
+        }
+        // Refresh the timer in case we are capturing user feedback.
+        if (researchLogger.isMakingUserRecording()) {
+            researchLogger.resetRecordingTimer();
         }
     }
 
@@ -1223,7 +1214,7 @@
             final RichInputConnection connection) {
         String word = "";
         if (connection != null) {
-            Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+            TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
             if (range != null) {
                 word = range.mWord.toString();
             }
@@ -1247,26 +1238,50 @@
     }
 
     /**
+     * Log a revert of onTextInput() (known in the IME as "EnteredText").
+     *
+     * SystemResponse: Remove the LogUnit recording the textInput
+     */
+    public static void latinIME_handleBackspace_cancelTextInput(final String text) {
+        final ResearchLogger researchLogger = getInstance();
+        researchLogger.uncommitCurrentLogUnit(text, true /* dumpCurrentLogUnit */);
+    }
+
+    /**
      * Log a call to LatinIME.pickSuggestionManually().
      *
      * UserAction: The user has chosen a specific word from the suggestion strip.
      */
     private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY =
             new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index",
-                    "suggestion", "x", "y", "isBatchMode");
+                    "suggestion", "x", "y", "isBatchMode", "score", "kind", "sourceDict");
+    /**
+     * Log a call to LatinIME.pickSuggestionManually().
+     *
+     * @param replacedWord the typed word that this manual suggestion replaces. May not be null.
+     * @param index the index in the suggestion strip
+     * @param suggestion the committed suggestion. May not be null.
+     * @param isBatchMode whether this was input in batch mode, aka gesture.
+     * @param score the internal score of the suggestion, as output by the dictionary
+     * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants
+     * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants.
+     */
     public static void latinIME_pickSuggestionManually(final String replacedWord,
-            final int index, final String suggestion, final boolean isBatchMode) {
+            final int index, final String suggestion, final boolean isBatchMode,
+            final int score, final int kind, final String sourceDict) {
         final ResearchLogger researchLogger = getInstance();
+        // Note : suggestion can't be null here, because it's only called in a place where it
+        // can't be null.
         if (!replacedWord.equals(suggestion.toString())) {
             // The user chose something other than what was already there.
-            researchLogger.setCurrentLogUnitContainsCorrection();
+            researchLogger.setCurrentLogUnitContainsUserDeletions();
             researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO);
         }
         final String scrubbedWord = scrubDigitsFromString(suggestion);
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY,
                 scrubDigitsFromString(replacedWord), index,
-                suggestion == null ? null : scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
-                Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode);
+                scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE,
+                Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict);
         researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode);
         researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis());
     }
@@ -1291,17 +1306,32 @@
     /**
      * Log a call to LatinIME.sendKeyCodePoint().
      *
-     * SystemResponse: The IME is inserting text into the TextView for numbers, fixed strings, or
-     * some other unusual mechanism.
+     * SystemResponse: The IME is inserting text into the TextView for non-word-constituent,
+     * strings (separators, numbers, other symbols).
      */
     private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT =
             new LogStatement("LatinIMESendKeyCodePoint", true, false, "code");
     public static void latinIME_sendKeyCodePoint(final int code) {
         final ResearchLogger researchLogger = getInstance();
-        researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
-                Constants.printableCode(scrubDigitFromCodePoint(code)));
-        if (Character.isDigit(code)) {
-            researchLogger.setCurrentLogUnitContainsDigitFlag();
+        final LogUnit phantomSpaceLogUnit = researchLogger.mPhantomSpaceLogUnit;
+        if (phantomSpaceLogUnit == null) {
+            researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
+                    Constants.printableCode(scrubDigitFromCodePoint(code)));
+            if (Character.isDigit(code)) {
+                researchLogger.setCurrentLogUnitContainsDigitFlag();
+            }
+            researchLogger.commitCurrentLogUnit();
+        } else {
+            researchLogger.enqueueEvent(phantomSpaceLogUnit, LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT,
+                    Constants.printableCode(scrubDigitFromCodePoint(code)));
+            if (Character.isDigit(code)) {
+                phantomSpaceLogUnit.setMayContainDigit();
+            }
+            researchLogger.mMainLogBuffer.shiftIn(phantomSpaceLogUnit);
+            if (researchLogger.mUserRecordingLogBuffer != null) {
+                researchLogger.mUserRecordingLogBuffer.shiftIn(phantomSpaceLogUnit);
+            }
+            researchLogger.mPhantomSpaceLogUnit = null;
         }
     }
 
@@ -1311,12 +1341,18 @@
      * SystemResponse: The IME is inserting a real space in place of a phantom space.
      */
     private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE =
-            new LogStatement("LatinIMEPromotPhantomSpace", false, false);
+            new LogStatement("LatinIMEPromotePhantomSpace", false, false);
     public static void latinIME_promotePhantomSpace() {
+        // A phantom space is always added before the text that triggered it.  The triggering text
+        // and the events that created it will be in mCurrentLogUnit, but the phantom space should
+        // be in its own LogUnit, committed before the triggering text.  Although it is created
+        // here, it is not added to the LogBuffer until the following call to
+        // latinIME_sendKeyCodePoint, because SENDKEYCODEPOINT LogStatement also must go into that
+        // LogUnit.
         final ResearchLogger researchLogger = getInstance();
-        final LogUnit logUnit;
-        logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
-        researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
+        researchLogger.mPhantomSpaceLogUnit = new LogUnit();
+        researchLogger.enqueueEvent(researchLogger.mPhantomSpaceLogUnit,
+                LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE);
     }
 
     /**
@@ -1402,23 +1438,40 @@
     public static void latinIME_revertCommit(final String committedWord,
             final String originallyTypedWord, final boolean isBatchMode,
             final String separatorString) {
+        // TODO: Prioritize adding a unit test for this method (as it is especially complex)
+        // TODO: Update the UserRecording LogBuffer as well as the MainLogBuffer
         final ResearchLogger researchLogger = getInstance();
-        // TODO: Verify that mCurrentLogUnit has been restored and contains the reverted word.
-        final LogUnit logUnit;
-        logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
-        if (originallyTypedWord.length() > 0 && hasLetters(originallyTypedWord)) {
-            if (logUnit != null) {
-                logUnit.setWords(originallyTypedWord);
-            }
+        //
+        // 1. Remove separator LogUnit
+        final LogUnit lastLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+        // Check that we're not at the beginning of input
+        if (lastLogUnit == null) return;
+        // Check that we're after a separator
+        if (lastLogUnit.getWordsAsString() != null) return;
+        // Remove separator
+        final LogUnit separatorLogUnit = researchLogger.mMainLogBuffer.unshiftIn();
+
+        // 2. Add revert LogStatement
+        final LogUnit revertedLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit();
+        if (revertedLogUnit == null) return;
+        if (!revertedLogUnit.getWordsAsString().equals(scrubDigitsFromString(committedWord))) {
+            // Any word associated with the reverted LogUnit has already had its digits scrubbed, so
+            // any digits in the committedWord argument must also be scrubbed for an accurate
+            // comparison.
+            return;
         }
-        researchLogger.enqueueEvent(logUnit != null ? logUnit : researchLogger.mCurrentLogUnit,
-                LOGSTATEMENT_LATINIME_REVERTCOMMIT, committedWord, originallyTypedWord,
-                separatorString);
-        if (logUnit != null) {
-            logUnit.setContainsCorrection();
-        }
+        researchLogger.enqueueEvent(revertedLogUnit, LOGSTATEMENT_LATINIME_REVERTCOMMIT,
+                committedWord, originallyTypedWord, separatorString);
+
+        // 3. Update the word associated with the LogUnit
+        revertedLogUnit.setWords(originallyTypedWord);
+        revertedLogUnit.setContainsUserDeletions();
+
+        // 4. Re-add the separator LogUnit
+        researchLogger.mMainLogBuffer.shiftIn(separatorLogUnit);
+
+        // 5. Record stats
         researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis());
-        researchLogger.commitCurrentLogUnitAsWord(originallyTypedWord, Long.MAX_VALUE, isBatchMode);
     }
 
     /**
@@ -1528,7 +1581,12 @@
     private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD =
             new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false);
     public static void richInputConnection_revertDoubleSpacePeriod() {
-        getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
+        final ResearchLogger researchLogger = getInstance();
+        // An extra LogUnit is added for the period; this is removed here because of the revert.
+        researchLogger.uncommitCurrentLogUnit(null, true /* dumpCurrentLogUnit */);
+        // TODO: This will probably be lost as the user backspaces further.  Figure out how to put
+        // it into the right logUnit.
+        researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD);
     }
 
     /**
@@ -1571,25 +1629,6 @@
     }
 
     private boolean isExpectingCommitText = false;
-    /**
-     * Log a call to (UnknownClass).commitPartialText
-     *
-     * SystemResponse: The IME is committing part of a word.  This happens if a space is
-     * automatically inserted to split a single typed string into two or more words.
-     */
-    // TODO: This method is currently unused.  Find where it should be called from in the IME and
-    // add invocations.
-    private static final LogStatement LOGSTATEMENT_COMMIT_PARTIAL_TEXT =
-            new LogStatement("CommitPartialText", true, false, "newCursorPosition");
-    public static void commitPartialText(final String committedWord,
-            final long lastTimestampOfWordData, final boolean isBatchMode) {
-        final ResearchLogger researchLogger = getInstance();
-        final String scrubbedWord = scrubDigitsFromString(committedWord);
-        researchLogger.enqueueEvent(LOGSTATEMENT_COMMIT_PARTIAL_TEXT);
-        researchLogger.mStatistics.recordAutoCorrection(SystemClock.uptimeMillis());
-        researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, lastTimestampOfWordData,
-                isBatchMode);
-    }
 
     /**
      * Log a call to RichInputConnection.commitText().
@@ -1613,12 +1652,24 @@
     }
 
     /**
-     * Shared event for logging committed text.
+     * Shared events for logging committed text.
+     *
+     * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules
+     * indicate that the word contents should not be logged.  It has no contents, and only serves to
+     * record the event and thereby make it easier to calculate word-level statistics even when the
+     * word contents are unknown.
      */
     private static final LogStatement LOGSTATEMENT_COMMITTEXT =
-            new LogStatement("CommitText", true, false, "committedText", "isBatchMode");
+            new LogStatement("CommitText", true /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */, "committedText", "isBatchMode");
+    private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED =
+            new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */,
+                    false /* isPotentiallyRevealing */);
     private void enqueueCommitText(final String word, final boolean isBatchMode) {
+        // Event containing the word; will be published only if privacy checks pass
         enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode);
+        // Event not containing the word; will always be published
+        enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED);
     }
 
     /**
@@ -1716,7 +1767,7 @@
     public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) {
         if (me != null) {
             getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT,
-                    me.toString());
+                    MotionEvent.obtain(me));
         }
     }
 
@@ -1752,7 +1803,7 @@
      */
     private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT =
             new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText",
-                    "enteredWordPos");
+                    "enteredWordPos", "suggestedWords");
     public static void latinIME_onEndBatchInput(final CharSequence enteredText,
             final int enteredWordPos, final SuggestedWords suggestedWords) {
         final ResearchLogger researchLogger = getInstance();
@@ -1760,23 +1811,32 @@
             researchLogger.mCurrentLogUnit.setWords(enteredText.toString());
         }
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText,
-                enteredWordPos);
+                enteredWordPos, suggestedWords);
         researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords);
         researchLogger.mStatistics.recordGestureInput(enteredText.length(),
                 SystemClock.uptimeMillis());
     }
 
+    private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
+            new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
     /**
      * Log a call to LatinIME.handleBackspace() that is not a batch delete.
      *
      * UserInput: The user is deleting one or more characters by hitting the backspace key once.
      * The covers single character deletes as well as deleting selections.
+     *
+     * @param numCharacters how many characters the backspace operation deleted
+     * @param shouldUncommitLogUnit whether to uncommit the last {@code LogUnit} in the
+     * {@code LogBuffer}
      */
-    private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE =
-            new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters");
-    public static void latinIME_handleBackspace(final int numCharacters) {
+    public static void latinIME_handleBackspace(final int numCharacters,
+            final boolean shouldUncommitLogUnit) {
         final ResearchLogger researchLogger = getInstance();
         researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters);
+        if (shouldUncommitLogUnit) {
+            ResearchLogger.getInstance().uncommitCurrentLogUnit(
+                    null, true /* dumpCurrentLogUnit */);
+        }
     }
 
     /**
@@ -1794,6 +1854,8 @@
                 numCharacters);
         researchLogger.mStatistics.recordGestureDelete(deletedText.length(),
                 SystemClock.uptimeMillis());
+        researchLogger.uncommitCurrentLogUnit(deletedText.toString(),
+                false /* dumpCurrentLogUnit */);
     }
 
     /**
@@ -1837,6 +1899,20 @@
     }
 
     /**
+     * Call this method when the logging system has attempted publication of an n-gram.
+     *
+     * Statistics are gathered about the success or failure.
+     *
+     * @param publishabilityResultCode a result code as defined by
+     * {@code MainLogBuffer.PUBLISHABILITY_*}
+     */
+    static void recordPublishabilityResultCode(final int publishabilityResultCode) {
+        final ResearchLogger researchLogger = getInstance();
+        final Statistics statistics = researchLogger.mStatistics;
+        statistics.recordPublishabilityResultCode(publishabilityResultCode);
+    }
+
+    /**
      * Log statistics.
      *
      * ContextualData, recorded at the end of a session.
@@ -1848,7 +1924,11 @@
                     "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete",
                     "dictionaryWordCount", "splitWordsCount", "gestureInputCount",
                     "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount",
-                    "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount");
+                    "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount",
+                    "publishableCount", "unpublishableStoppingCount",
+                    "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount",
+                    "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount",
+                    "unpublishableNotInDictionaryCount");
     private static void logStatistics() {
         final ResearchLogger researchLogger = getInstance();
         final Statistics statistics = researchLogger.mStatistics;
@@ -1863,6 +1943,10 @@
                 statistics.mGesturesInputCount, statistics.mGesturesCharsCount,
                 statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount,
                 statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount,
-                statistics.mAutoCorrectionsCount);
+                statistics.mAutoCorrectionsCount, statistics.mPublishableCount,
+                statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount,
+                statistics.mUnpublishableSampledTooRecently,
+                statistics.mUnpublishableDictionaryUnavailable,
+                statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary);
     }
 }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index 7f6c851..fd323a1 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -21,6 +21,8 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
+import java.util.concurrent.TimeUnit;
+
 public class Statistics {
     private static final String TAG = Statistics.class.getSimpleName();
     private static final boolean DEBUG = false
@@ -61,6 +63,16 @@
     boolean mIsEmptyUponStarting;
     boolean mIsEmptinessStateKnown;
 
+    // Counts of how often an n-gram is collected or not, and the reasons for the decision.
+    // Keep consistent with publishability result code list in MainLogBuffer
+    int mPublishableCount;
+    int mUnpublishableStoppingCount;
+    int mUnpublishableIncorrectWordCount;
+    int mUnpublishableSampledTooRecently;
+    int mUnpublishableDictionaryUnavailable;
+    int mUnpublishableMayContainDigit;
+    int mUnpublishableNotInDictionary;
+
     // Timers to count average time to enter a key, first press a delete key,
     // between delete keys, and then to return typing after a delete key.
     final AverageTimeCounter mKeyCounter = new AverageTimeCounter();
@@ -92,8 +104,8 @@
 
     // To account for the interruptions when the user's attention is directed elsewhere, times
     // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
-    public static final int MIN_TYPING_INTERMISSION = 2 * 1000;  // in milliseconds
-    public static final int MIN_DELETION_INTERMISSION = 10 * 1000;  // in milliseconds
+    public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
+    public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
 
     // The last time that a tap was performed
     private long mLastTapTime;
@@ -133,6 +145,13 @@
         mAfterDeleteKeyCounter.reset();
         mGesturesCharsCount = 0;
         mGesturesDeletedCount = 0;
+        mPublishableCount = 0;
+        mUnpublishableStoppingCount = 0;
+        mUnpublishableIncorrectWordCount = 0;
+        mUnpublishableSampledTooRecently = 0;
+        mUnpublishableDictionaryUnavailable = 0;
+        mUnpublishableMayContainDigit = 0;
+        mUnpublishableNotInDictionary = 0;
 
         mLastTapTime = 0;
         mIsLastKeyDeleteKey = false;
@@ -230,4 +249,31 @@
         mIsLastKeyDeleteKey = isDeletion;
         mLastTapTime = time;
     }
+
+    public void recordPublishabilityResultCode(final int publishabilityResultCode) {
+        // Keep consistent with publishability result code list in MainLogBuffer
+        switch (publishabilityResultCode) {
+        case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE:
+            mPublishableCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING:
+            mUnpublishableStoppingCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT:
+            mUnpublishableIncorrectWordCount++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY:
+            mUnpublishableSampledTooRecently++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE:
+            mUnpublishableDictionaryUnavailable++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT:
+            mUnpublishableMayContainDigit++;
+            break;
+        case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY:
+            mUnpublishableNotInDictionary++;
+            break;
+        }
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java
index ba05ec1..c7ea3e6 100644
--- a/java/src/com/android/inputmethod/research/Uploader.java
+++ b/java/src/com/android/inputmethod/research/Uploader.java
@@ -49,7 +49,7 @@
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
-    private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
+    private static final boolean IS_INHIBITING_UPLOAD = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     private static final int BUF_SIZE = 1024 * 8;
 
@@ -76,7 +76,7 @@
     }
 
     public boolean isPossibleToUpload() {
-        return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_AUTO_UPLOAD;
+        return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD;
     }
 
     private boolean hasUploadingPermission() {
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index d2db349..fd3f2f6 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -24,8 +24,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 
-import com.android.inputmethod.latin.define.ProductionFlag;
-
 /**
  * Service to invoke the uploader.
  *
@@ -33,12 +31,9 @@
  */
 public final class UploaderService extends IntentService {
     private static final String TAG = UploaderService.class.getSimpleName();
-    private static final boolean DEBUG = false
-            && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
     public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
     public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
             + ".extra.UPLOAD_UNCONDITIONALLY";
-    protected static final int TIMEOUT_IN_MS = 1000 * 4;
 
     public UploaderService() {
         super("Research Uploader Service");
diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
new file mode 100644
index 0000000..78ed668
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/ui/SplashScreen.java
@@ -0,0 +1,111 @@
+/*
+ * 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.research.ui;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.IBinder;
+import android.view.Window;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.inputmethod.latin.R.string;
+
+/**
+ * Show a dialog when the user first opens the keyboard.
+ *
+ * The splash screen is a modal dialog box presented when the user opens this keyboard for the first
+ * time.  It is useful for giving specific warnings that must be shown to the user before use.
+ *
+ * While the splash screen does share with the setup wizard the common goal of presenting
+ * information to the user before use, they are presented at different times and with different
+ * capabilities.  The setup wizard is launched by tapping on the icon, and walks the user through
+ * the setup process.  It can, however, be bypassed by enabling the keyboard from Settings directly.
+ * The splash screen cannot be bypassed, and is therefore more appropriate for obtaining user
+ * consent.
+ */
+public class SplashScreen {
+    public interface UserConsentListener {
+        public void onSplashScreenUserClickedOk();
+    }
+
+    final UserConsentListener mListener;
+    final Dialog mSplashDialog;
+
+    public SplashScreen(final InputMethodService inputMethodService,
+            final UserConsentListener listener) {
+        mListener = listener;
+        final Builder builder = new Builder(inputMethodService)
+                .setTitle(string.research_splash_title)
+                .setMessage(string.research_splash_content)
+                .setPositiveButton(android.R.string.yes,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                mListener.onSplashScreenUserClickedOk();
+                                mSplashDialog.dismiss();
+                            }
+                })
+                .setNegativeButton(android.R.string.no,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                final String packageName = inputMethodService.getPackageName();
+                                final Uri packageUri = Uri.parse("package:" + packageName);
+                                final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE,
+                                        packageUri);
+                                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                                inputMethodService.startActivity(intent);
+                            }
+                })
+                .setCancelable(true)
+                .setOnCancelListener(
+                        new OnCancelListener() {
+                            @Override
+                            public void onCancel(DialogInterface dialog) {
+                                inputMethodService.requestHideSelf(0);
+                            }
+                });
+        mSplashDialog = builder.create();
+    }
+
+    /**
+     * Show the splash screen.
+     *
+     * The user must consent to the terms presented in the SplashScreen before they can use the
+     * keyboard.  If they cancel instead, they are given the option to uninstall the keybard.
+     *
+     * @param windowToken {@link IBinder} to attach dialog to
+     */
+    public void showSplashScreen(final IBinder windowToken) {
+        final Window window = mSplashDialog.getWindow();
+        final LayoutParams lp = window.getAttributes();
+        lp.token = windowToken;
+        lp.type = LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+        window.setAttributes(lp);
+        window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        mSplashDialog.show();
+    }
+
+    public boolean isShowing() {
+        return mSplashDialog.isShowing();
+    }
+}
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index cbe9515..acd230f 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -46,33 +46,45 @@
     jni_common.cpp
 
 LATIN_IME_CORE_SRC_FILES := \
-    additional_proximity_chars.cpp \
-    bigram_dictionary.cpp \
-    char_utils.cpp \
-    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/core/suggest.cpp \
     $(addprefix suggest/core/dicnode/, \
         dic_node.cpp \
         dic_node_utils.cpp \
         dic_nodes_cache.cpp) \
+    $(addprefix suggest/core/dictionary/, \
+        bigram_dictionary.cpp \
+        binary_dictionary_format_utils.cpp \
+        binary_dictionary_header.cpp \
+        binary_dictionary_header_reading_utils.cpp \
+        binary_dictionary_terminal_attributes_reading_utils.cpp \
+        bloom_filter.cpp \
+        byte_array_utils.cpp \
+        dictionary.cpp \
+        digraph_utils.cpp \
+        multi_bigram_map.cpp) \
+    $(addprefix suggest/core/layout/, \
+        additional_proximity_chars.cpp \
+        proximity_info.cpp \
+        proximity_info_params.cpp \
+        proximity_info_state.cpp \
+        proximity_info_state_utils.cpp) \
     suggest/core/policy/weighting.cpp \
     suggest/core/session/dic_traverse_session.cpp \
+    $(addprefix suggest/policyimpl/dictionary/, \
+        dynamic_patricia_trie_policy.cpp \
+        patricia_trie_policy.cpp \
+        patricia_trie_reading_utils.cpp) \
     suggest/policyimpl/gesture/gesture_suggest_policy_factory.cpp \
     $(addprefix suggest/policyimpl/typing/, \
         scoring_params.cpp \
         typing_scoring.cpp \
         typing_suggest_policy.cpp \
         typing_traversal.cpp \
-        typing_weighting.cpp)
+        typing_weighting.cpp) \
+    $(addprefix utils/, \
+        autocorrection_threshold_utils.cpp \
+        char_utils.cpp \
+        log_utils.cpp)
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index dedb02a..f88d37e 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -17,10 +17,11 @@
 #define LOG_TAG "LatinIME: jni: ProximityInfo"
 
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
+
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
-#include "proximity_info.h"
+#include "suggest/core/layout/proximity_info.h"
 
 namespace latinime {
 
@@ -42,13 +43,17 @@
     delete pi;
 }
 
-static JNINativeMethod sMethods[] = {
-    {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)}
+static const JNINativeMethod sMethods[] = {
+    {
+        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 1dd68ea..6e1b80e 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -14,36 +14,43 @@
  * limitations under the License.
  */
 
-#include <cstring> // for memset()
-
 #define LOG_TAG "LatinIME: jni: BinaryDictionary"
 
-#include "defines.h" // for macros below
+#include "com_android_inputmethod_latin_BinaryDictionary.h"
 
-#ifdef USE_MMAP_FOR_DICTIONARY
 #include <cerrno>
+#include <cstring> // for memset()
 #include <fcntl.h>
 #include <sys/mman.h>
-#else // USE_MMAP_FOR_DICTIONARY
-#include <cstdlib>
-#include <cstdio> // for fopen() etc.
-#endif // USE_MMAP_FOR_DICTIONARY
+#include <unistd.h>
 
-#include "binary_format.h"
-#include "com_android_inputmethod_latin_BinaryDictionary.h"
-#include "correction.h"
-#include "dictionary.h"
+#include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
+#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/suggest_options.h"
+#include "utils/autocorrection_threshold_utils.h"
 
 namespace latinime {
 
 class ProximityInfo;
 
-static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd);
+// Helper method
+static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
+    int ret = munmap(const_cast<void *>(dictBuf), length);
+    if (ret != 0) {
+        AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+    }
+    ret = close(fd);
+    if (ret != 0) {
+        AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
+    }
+}
 
 static jlong latinime_BinaryDictionary_open(JNIEnv *env, jclass clazz, jstring sourceDir,
-        jlong dictOffset, jlong dictSize) {
+        jlong dictOffset, jlong dictSize, jboolean isUpdatable) {
     PROF_OPEN;
     PROF_START(66);
     const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir);
@@ -56,86 +63,68 @@
     sourceDirChars[sourceDirUtf8Length] = '\0';
     int fd = 0;
     void *dictBuf = 0;
-    int adjust = 0;
-#ifdef USE_MMAP_FOR_DICTIONARY
-    /* mmap version */
-    fd = open(sourceDirChars, O_RDONLY);
+    int offset = 0;
+    const bool updatableMmap = (isUpdatable == JNI_TRUE);
+    const int openMode = updatableMmap ? O_RDWR : O_RDONLY;
+    fd = open(sourceDirChars, openMode);
     if (fd < 0) {
         AKLOGE("DICT: Can't open sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
         return 0;
     }
     int pagesize = getpagesize();
-    adjust = static_cast<int>(dictOffset) % pagesize;
-    int adjDictOffset = static_cast<int>(dictOffset) - adjust;
-    int adjDictSize = static_cast<int>(dictSize) + adjust;
-    dictBuf = mmap(0, adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset);
+    offset = static_cast<int>(dictOffset) % pagesize;
+    int adjDictOffset = static_cast<int>(dictOffset) - offset;
+    int adjDictSize = static_cast<int>(dictSize) + offset;
+    const int protMode = updatableMmap ? PROT_READ | PROT_WRITE : PROT_READ;
+    dictBuf = mmap(0, adjDictSize, protMode, MAP_PRIVATE, fd, adjDictOffset);
     if (dictBuf == MAP_FAILED) {
         AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno);
         return 0;
     }
-    dictBuf = static_cast<char *>(dictBuf) + adjust;
-#else // USE_MMAP_FOR_DICTIONARY
-    /* malloc version */
-    FILE *file = 0;
-    file = fopen(sourceDirChars, "rb");
-    if (file == 0) {
-        AKLOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno);
-        return 0;
-    }
-    dictBuf = malloc(dictSize);
-    if (!dictBuf) {
-        AKLOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno);
-        return 0;
-    }
-    int ret = fseek(file, static_cast<long>(dictOffset), SEEK_SET);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno);
-        return 0;
-    }
-    ret = fread(dictBuf, dictSize, 1, file);
-    if (ret != 1) {
-        AKLOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno);
-        return 0;
-    }
-    ret = fclose(file);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in fclose. ret=%d errno=%d", ret, errno);
-        return 0;
-    }
-#endif // USE_MMAP_FOR_DICTIONARY
+    dictBuf = static_cast<char *>(dictBuf) + offset;
     if (!dictBuf) {
         AKLOGE("DICT: dictBuf is null");
         return 0;
     }
     Dictionary *dictionary = 0;
-    if (BinaryFormat::UNKNOWN_FORMAT
-            == BinaryFormat::detectFormat(static_cast<uint8_t *>(dictBuf),
+    if (BinaryDictionaryFormatUtils::UNKNOWN_VERSION
+            == BinaryDictionaryFormatUtils::detectFormatVersion(static_cast<uint8_t *>(dictBuf),
                     static_cast<int>(dictSize))) {
         AKLOGE("DICT: dictionary format is unknown, bad magic number");
-#ifdef USE_MMAP_FOR_DICTIONARY
-        releaseDictBuf(static_cast<const char *>(dictBuf) - adjust, adjDictSize, fd);
-#else // USE_MMAP_FOR_DICTIONARY
-        releaseDictBuf(dictBuf, 0, 0);
-#endif // USE_MMAP_FOR_DICTIONARY
+        releaseDictBuf(static_cast<const char *>(dictBuf) - offset, adjDictSize, fd);
     } else {
-        dictionary = new Dictionary(dictBuf, static_cast<int>(dictSize), fd, adjust);
+        dictionary = new Dictionary(env, dictBuf, static_cast<int>(dictSize), fd, offset,
+                updatableMmap);
     }
     PROF_END(66);
     PROF_CLOSE;
     return reinterpret_cast<jlong>(dictionary);
 }
 
+static void latinime_BinaryDictionary_close(JNIEnv *env, jclass clazz, jlong dict) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) return;
+    const BinaryDictionaryInfo *const binaryDictionaryInfo = dictionary->getBinaryDictionaryInfo();
+    const int dictBufOffset = binaryDictionaryInfo->getDictBufOffset();
+    const void *dictBuf = binaryDictionaryInfo->getDictBuf();
+    if (!dictBuf) return;
+    releaseDictBuf(static_cast<const char *>(dictBuf) - dictBufOffset,
+            binaryDictionaryInfo->getDictSize() + dictBufOffset,
+            binaryDictionaryInfo->getMmapFd());
+    delete dictionary;
+}
+
 static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jclass clazz, jlong dict,
         jlong proximityInfo, jlong dicTraverseSession, jintArray xCoordinatesArray,
         jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray,
-        jintArray inputCodePointsArray, jint inputSize, jint commitPoint, jboolean isGesture,
-        jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance,
-        jintArray outputCodePointsArray, jintArray scoresArray, jintArray spaceIndicesArray,
-        jintArray outputTypesArray) {
+        jintArray inputCodePointsArray, jint inputSize, jint commitPoint, jintArray suggestOptions,
+        jintArray prevWordCodePointsForBigrams, jintArray outputCodePointsArray,
+        jintArray scoresArray, jintArray spaceIndicesArray, jintArray outputTypesArray) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return 0;
     ProximityInfo *pInfo = reinterpret_cast<ProximityInfo *>(proximityInfo);
-    void *traverseSession = reinterpret_cast<void *>(dicTraverseSession);
+    DicTraverseSession *traverseSession =
+            reinterpret_cast<DicTraverseSession *>(dicTraverseSession);
 
     // Input values
     int xCoordinates[inputSize];
@@ -159,6 +148,11 @@
         prevWordCodePoints = prevWordCodePointsInternal;
     }
 
+    const jsize numberOfOptions = env->GetArrayLength(suggestOptions);
+    int options[numberOfOptions];
+    env->GetIntArrayRegion(suggestOptions, 0, numberOfOptions, options);
+    SuggestOptions givenSuggestOptions(options, numberOfOptions);
+
     // Output values
     /* By the way, let's check the output array length here to make sure */
     const jsize outputCodePointsLength = env->GetArrayLength(outputCodePointsArray);
@@ -185,11 +179,11 @@
     memset(outputTypes, 0, sizeof(outputTypes));
 
     int count;
-    if (isGesture || inputSize > 0) {
+    if (givenSuggestOptions.isGesture() || inputSize > 0) {
         count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates,
                 times, pointerIds, inputCodePoints, inputSize, prevWordCodePoints,
-                prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance,
-                outputCodePoints, scores, spaceIndices, outputTypes);
+                prevWordCodePointsLength, commitPoint, &givenSuggestOptions, outputCodePoints,
+                scores, spaceIndices, outputTypes);
     } else {
         count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength,
                 inputCodePoints, inputSize, outputCodePoints, scores, outputTypes);
@@ -205,26 +199,26 @@
 }
 
 static jint latinime_BinaryDictionary_getProbability(JNIEnv *env, jclass clazz, jlong dict,
-        jintArray wordArray) {
+        jintArray word) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
-    if (!dictionary) return 0;
-    const jsize codePointLength = env->GetArrayLength(wordArray);
-    int codePoints[codePointLength];
-    env->GetIntArrayRegion(wordArray, 0, codePointLength, codePoints);
-    return dictionary->getProbability(codePoints, codePointLength);
+    if (!dictionary) return NOT_A_PROBABILITY;
+    const jsize wordLength = env->GetArrayLength(word);
+    int codePoints[wordLength];
+    env->GetIntArrayRegion(word, 0, wordLength, codePoints);
+    return dictionary->getProbability(codePoints, wordLength);
 }
 
 static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jclass clazz, jlong dict,
-        jintArray wordArray1, jintArray wordArray2) {
+        jintArray word0, jintArray word1) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
     if (!dictionary) return JNI_FALSE;
-    const jsize codePointLength1 = env->GetArrayLength(wordArray1);
-    const jsize codePointLength2 = env->GetArrayLength(wordArray2);
-    int codePoints1[codePointLength1];
-    int codePoints2[codePointLength2];
-    env->GetIntArrayRegion(wordArray1, 0, codePointLength1, codePoints1);
-    env->GetIntArrayRegion(wordArray2, 0, codePointLength2, codePoints2);
-    return dictionary->isValidBigram(codePoints1, codePointLength1, codePoints2, codePointLength2);
+    const jsize word0Length = env->GetArrayLength(word0);
+    const jsize word1Length = env->GetArrayLength(word1);
+    int word0CodePoints[word0Length];
+    int word1CodePoints[word1Length];
+    env->GetIntArrayRegion(word0, 0, word0Length, word0CodePoints);
+    env->GetIntArrayRegion(word1, 0, word1Length, word1CodePoints);
+    return dictionary->isValidBigram(word0CodePoints, word0Length, word1CodePoints, word1Length);
 }
 
 static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jclass clazz,
@@ -235,7 +229,7 @@
     int afterCodePoints[afterLength];
     env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
     env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
-    return Correction::RankingAlgorithm::calcNormalizedScore(beforeCodePoints, beforeLength,
+    return AutocorrectionThresholdUtils::calcNormalizedScore(beforeCodePoints, beforeLength,
             afterCodePoints, afterLength, score);
 }
 
@@ -247,61 +241,100 @@
     int afterCodePoints[afterLength];
     env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints);
     env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints);
-    return Correction::RankingAlgorithm::editDistance(beforeCodePoints, beforeLength,
+    return AutocorrectionThresholdUtils::editDistance(beforeCodePoints, beforeLength,
             afterCodePoints, afterLength);
 }
 
-static void latinime_BinaryDictionary_close(JNIEnv *env, jclass clazz, jlong dict) {
+static void latinime_BinaryDictionary_addUnigramWord(JNIEnv *env, jclass clazz, jlong dict,
+        jintArray word, jint probability) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
-    if (!dictionary) return;
-    const void *dictBuf = dictionary->getDict();
-    if (!dictBuf) return;
-#ifdef USE_MMAP_FOR_DICTIONARY
-    releaseDictBuf(static_cast<const char *>(dictBuf) - dictionary->getDictBufAdjust(),
-            dictionary->getDictSize() + dictionary->getDictBufAdjust(), dictionary->getMmapFd());
-#else // USE_MMAP_FOR_DICTIONARY
-    releaseDictBuf(dictBuf, 0, 0);
-#endif // USE_MMAP_FOR_DICTIONARY
-    delete dictionary;
+    if (!dictionary) {
+        return;
+    }
+    jsize wordLength = env->GetArrayLength(word);
+    int codePoints[wordLength];
+    dictionary->addUnigramWord(codePoints, wordLength, probability);
 }
 
-static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd) {
-#ifdef USE_MMAP_FOR_DICTIONARY
-    int ret = munmap(const_cast<void *>(dictBuf), length);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in munmap. ret=%d errno=%d", ret, errno);
+static void latinime_BinaryDictionary_addBigramWords(JNIEnv *env, jclass clazz, jlong dict,
+        jintArray word0, jintArray word1, jint probability) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return;
     }
-    ret = close(fd);
-    if (ret != 0) {
-        AKLOGE("DICT: Failure in close. ret=%d errno=%d", ret, errno);
-    }
-#else // USE_MMAP_FOR_DICTIONARY
-    free(const_cast<void *>(dictBuf));
-#endif // USE_MMAP_FOR_DICTIONARY
+    jsize word0Length = env->GetArrayLength(word0);
+    int word0CodePoints[word0Length];
+    jsize word1Length = env->GetArrayLength(word1);
+    int word1CodePoints[word1Length];
+    dictionary->addBigramWords(word0CodePoints, word0Length, word1CodePoints,
+            word1Length, probability);
 }
 
-static JNINativeMethod sMethods[] = {
-    {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)}
+static void latinime_BinaryDictionary_removeBigramWords(JNIEnv *env, jclass clazz, jlong dict,
+        jintArray word0, jintArray word1) {
+    Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
+    if (!dictionary) {
+        return;
+    }
+    jsize word0Length = env->GetArrayLength(word0);
+    int word0CodePoints[word0Length];
+    jsize word1Length = env->GetArrayLength(word1);
+    int word1CodePoints[word1Length];
+    dictionary->removeBigramWords(word0CodePoints, word0Length, word1CodePoints,
+            word1Length);
+}
+
+static const JNINativeMethod sMethods[] = {
+    {
+        const_cast<char *>("openNative"),
+        const_cast<char *>("(Ljava/lang/String;JJZ)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[III[I[I[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)
+    },
+    {
+        const_cast<char *>("addUnigramWordNative"),
+        const_cast<char *>("(J[II)V"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_addUnigramWord)
+    },
+    {
+        const_cast<char *>("addBigramWordsNative"),
+        const_cast<char *>("(J[I[II)V"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_addBigramWords)
+    },
+    {
+        const_cast<char *>("removeBigramWordsNative"),
+        const_cast<char *>("(J[I[I)V"),
+        reinterpret_cast<void *>(latinime_BinaryDictionary_removeBigramWords)
+    }
 };
 
 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 dfe3b09..72e6258 100644
--- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
+++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp
@@ -17,46 +17,55 @@
 #define LOG_TAG "LatinIME: jni: Session"
 
 #include "com_android_inputmethod_latin_DicTraverseSession.h"
+
 #include "defines.h"
-#include "dic_traverse_wrapper.h"
 #include "jni.h"
 #include "jni_common.h"
+#include "suggest/core/session/dic_traverse_session.h"
 
 namespace latinime {
 class Dictionary;
 static jlong latinime_setDicTraverseSession(JNIEnv *env, jclass clazz, jstring localeJStr) {
-    void *traverseSession = DicTraverseWrapper::getDicTraverseSession(env, localeJStr);
+    void *traverseSession = DicTraverseSession::getSessionInstance(env, localeJStr);
     return reinterpret_cast<jlong>(traverseSession);
 }
 
 static void latinime_initDicTraverseSession(JNIEnv *env, jclass clazz, jlong traverseSession,
         jlong dictionary, jintArray previousWord, jint previousWordLength) {
-    void *ts = reinterpret_cast<void *>(traverseSession);
+    DicTraverseSession *ts = reinterpret_cast<DicTraverseSession *>(traverseSession);
     Dictionary *dict = reinterpret_cast<Dictionary *>(dictionary);
     if (!previousWord) {
-        DicTraverseWrapper::initDicTraverseSession(ts, dict, 0, 0);
+        DicTraverseSession::initSessionInstance(
+                ts, dict, 0 /* prevWord */, 0 /* prevWordLength*/, 0 /* suggestOptions */);
         return;
     }
     int prevWord[previousWordLength];
     env->GetIntArrayRegion(previousWord, 0, previousWordLength, prevWord);
-    DicTraverseWrapper::initDicTraverseSession(ts, dict, prevWord, previousWordLength);
+    DicTraverseSession::initSessionInstance(
+            ts, dict, prevWord, previousWordLength, 0 /* suggestOptions */);
 }
 
 static void latinime_releaseDicTraverseSession(JNIEnv *env, jclass clazz, jlong traverseSession) {
-    void *ts = reinterpret_cast<void *>(traverseSession);
-    DicTraverseWrapper::releaseDicTraverseSession(ts);
+    DicTraverseSession *ts = reinterpret_cast<DicTraverseSession *>(traverseSession);
+    DicTraverseSession::releaseSessionInstance(ts);
 }
 
-static JNINativeMethod sMethods[] = {
-    {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)}
+static const JNINativeMethod sMethods[] = {
+    {
+        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 8e5c508..f2867d7 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -55,8 +55,8 @@
 }
 
 namespace latinime {
-int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
-        int numMethods) {
+int registerNativeMethods(JNIEnv *env, const char *const className, const JNINativeMethod *methods,
+        const int numMethods) {
     jclass clazz = env->FindClass(className);
     if (!clazz) {
         AKLOGE("Native registration unable to find class '%s'", className);
diff --git a/native/jni/jni_common.h b/native/jni/jni_common.h
index f960b05..ef72a7c 100644
--- a/native/jni/jni_common.h
+++ b/native/jni/jni_common.h
@@ -20,7 +20,7 @@
 #include "jni.h"
 
 namespace latinime {
-int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods,
-        int numMethods);
+int registerNativeMethods(JNIEnv *env, const char *const className, const JNINativeMethod *methods,
+        const int numMethods);
 } // namespace latinime
 #endif // LATINIME_JNI_COMMON_H
diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h
deleted file mode 100644
index 9824153..0000000
--- a/native/jni/src/binary_format.h
+++ /dev/null
@@ -1,778 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_BINARY_FORMAT_H
-#define LATINIME_BINARY_FORMAT_H
-
-#include <cstdlib>
-#include <map>
-#include <stdint.h>
-
-#include "bloom_filter.h"
-#include "char_utils.h"
-#include "hash_map_compat.h"
-
-namespace latinime {
-
-class BinaryFormat {
- public:
-    // Mask and flags for children address type selection.
-    static const int MASK_GROUP_ADDRESS_TYPE = 0xC0;
-
-    // Flag for single/multiple char group
-    static const int FLAG_HAS_MULTIPLE_CHARS = 0x20;
-
-    // Flag for terminal groups
-    static const int FLAG_IS_TERMINAL = 0x10;
-
-    // Flag for shortcut targets presence
-    static const int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
-    // Flag for bigram presence
-    static const int FLAG_HAS_BIGRAMS = 0x04;
-    // Flag for non-words (typically, shortcut only entries)
-    static const int FLAG_IS_NOT_A_WORD = 0x02;
-    // Flag for blacklist
-    static const int FLAG_IS_BLACKLISTED = 0x01;
-
-    // Attribute (bigram/shortcut) related flags:
-    // Flag for presence of more attributes
-    static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
-    // Flag for sign of offset. If this flag is set, the offset value must be negated.
-    static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
-
-    // Mask for attribute probability, stored on 4 bits inside the flags byte.
-    static const int MASK_ATTRIBUTE_PROBABILITY = 0x0F;
-    // The numeric value of the shortcut probability that means 'whitelist'.
-    static const int WHITELIST_SHORTCUT_PROBABILITY = 15;
-
-    // Mask and flags for attribute address type selection.
-    static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
-
-    static const int UNKNOWN_FORMAT = -1;
-    static const int SHORTCUT_LIST_SIZE_SIZE = 2;
-
-    static int detectFormat(const uint8_t *const dict, const int dictSize);
-    static int getHeaderSize(const uint8_t *const dict, const int dictSize);
-    static int getFlags(const uint8_t *const dict, const int dictSize);
-    static bool hasBlacklistedOrNotAWordFlag(const int flags);
-    static void readHeaderValue(const uint8_t *const dict, const int dictSize,
-            const char *const key, int *outValue, const int outValueSize);
-    static int readHeaderValueInt(const uint8_t *const dict, const int dictSize,
-            const char *const key);
-    static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
-    static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
-    static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
-    static int readProbabilityWithoutMovingPointer(const uint8_t *const dict, const int pos);
-    static int skipOtherCharacters(const uint8_t *const dict, const int pos);
-    static int skipChildrenPosition(const uint8_t flags, const int pos);
-    static int skipProbability(const uint8_t flags, const int pos);
-    static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
-            const int pos);
-    static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static bool hasChildrenInFlags(const uint8_t flags);
-    static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags,
-            int *pos);
-    static int getAttributeProbabilityFromFlags(const int flags);
-    static int getTerminalPosition(const uint8_t *const root, const int *const inWord,
-            const int length, const bool forceLowerCaseSearch);
-    static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth,
-            int *outWord, int *outUnigramProbability);
-    static int computeProbabilityForBigram(
-            const int unigramProbability, const int bigramProbability);
-    static int getProbability(const int position, const std::map<int, int> *bigramMap,
-            const uint8_t *bigramFilter, const int unigramProbability);
-    static int getBigramProbabilityFromHashMap(const int position,
-            const hash_map_compat<int, int> *bigramMap, const int unigramProbability);
-    static float getMultiWordCostMultiplier(const uint8_t *const dict, const int dictSize);
-    static void fillBigramProbabilityToHashMap(const uint8_t *const root, int position,
-            hash_map_compat<int, int> *bigramMap);
-    static int getBigramProbability(const uint8_t *const root, int position,
-            const int nextPosition, const int unigramProbability);
-
-    // Flags for special processing
-    // Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or
-    // something very bad (like, the apocalypse) will happen. Please update both at the same time.
-    enum {
-        REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1,
-        REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4
-    };
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
-    static int getBigramListPositionForWordPosition(const uint8_t *const root, int position);
-
-    static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
-    static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
-    static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
-    static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
-    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
-
-    // Any file smaller than this is not a dictionary.
-    static const int DICTIONARY_MINIMUM_SIZE = 4;
-    // Originally, format version 1 had a 16-bit magic number, then the version number `01'
-    // then options that must be 0. Hence the first 32-bits of the format are always as follow
-    // and it's okay to consider them a magic number as a whole.
-    static const int FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100;
-    static const int FORMAT_VERSION_1_HEADER_SIZE = 5;
-    // The versions of Latin IME that only handle format version 1 only test for the magic
-    // number, so we had to change it so that version 2 files would be rejected by older
-    // implementations. On this occasion, we made the magic number 32 bits long.
-    static const int FORMAT_VERSION_2_MAGIC_NUMBER = -1681835266; // 0x9BC13AFE
-    // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
-    static const int FORMAT_VERSION_2_MINIMUM_SIZE = 12;
-
-    static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1;
-    static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
-    static const int CHARACTER_ARRAY_TERMINATOR = 0x1F;
-    static const int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
-    static const int NO_FLAGS = 0;
-    static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
-    static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
-};
-
-AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict, const int dictSize) {
-    // The magic number is stored big-endian.
-    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
-    // understand this format.
-    if (dictSize < DICTIONARY_MINIMUM_SIZE) return UNKNOWN_FORMAT;
-    const int magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3];
-    switch (magicNumber) {
-    case FORMAT_VERSION_1_MAGIC_NUMBER:
-        // Format 1 header is exactly 5 bytes long and looks like:
-        // Magic number (2 bytes) 0x78 0xB1
-        // Version number (1 byte) 0x01
-        // Options (2 bytes) must be 0x00 0x00
-        return 1;
-    case FORMAT_VERSION_2_MAGIC_NUMBER:
-        // Version 2 dictionaries are at least 12 bytes long (see below details for the header).
-        // If this dictionary has the version 2 magic number but is less than 12 bytes long, then
-        // it's an unknown format and we need to avoid confidently reading the next bytes.
-        if (dictSize < FORMAT_VERSION_2_MINIMUM_SIZE) return UNKNOWN_FORMAT;
-        // Format 2 header is as follows:
-        // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
-        // Version number (2 bytes) 0x00 0x02
-        // Options (2 bytes)
-        // Header size (4 bytes) : integer, big endian
-        return (dict[4] << 8) + dict[5];
-    default:
-        return UNKNOWN_FORMAT;
-    }
-}
-
-inline int BinaryFormat::getFlags(const uint8_t *const dict, const int dictSize) {
-    switch (detectFormat(dict, dictSize)) {
-    case 1:
-        return NO_FLAGS; // TODO: NO_FLAGS is unused anywhere else?
-    default:
-        return (dict[6] << 8) + dict[7];
-    }
-}
-
-inline bool BinaryFormat::hasBlacklistedOrNotAWordFlag(const int flags) {
-    return (flags & (FLAG_IS_BLACKLISTED | FLAG_IS_NOT_A_WORD)) != 0;
-}
-
-inline int BinaryFormat::getHeaderSize(const uint8_t *const dict, const int dictSize) {
-    switch (detectFormat(dict, dictSize)) {
-    case 1:
-        return FORMAT_VERSION_1_HEADER_SIZE;
-    case 2:
-        // See the format of the header in the comment in detectFormat() above
-        return (dict[8] << 24) + (dict[9] << 16) + (dict[10] << 8) + dict[11];
-    default:
-        return S_INT_MAX;
-    }
-}
-
-inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const int dictSize,
-        const char *const key, int *outValue, const int outValueSize) {
-    int outValueIndex = 0;
-    // Only format 2 and above have header attributes as {key,value} string pairs. For prior
-    // formats, we just return an empty string, as if the key wasn't found.
-    if (2 <= detectFormat(dict, dictSize)) {
-        const int headerOptionsOffset = 4 /* magic number */
-                + 2 /* dictionary version */ + 2 /* flags */;
-        const int headerSize =
-                (dict[headerOptionsOffset] << 24) + (dict[headerOptionsOffset + 1] << 16)
-                + (dict[headerOptionsOffset + 2] << 8) + dict[headerOptionsOffset + 3];
-        const int headerEnd = headerOptionsOffset + 4 + headerSize;
-        int index = headerOptionsOffset + 4;
-        while (index < headerEnd) {
-            int keyIndex = 0;
-            int codePoint = getCodePointAndForwardPointer(dict, &index);
-            while (codePoint != NOT_A_CODE_POINT) {
-                if (codePoint != key[keyIndex++]) {
-                    break;
-                }
-                codePoint = getCodePointAndForwardPointer(dict, &index);
-            }
-            if (codePoint == NOT_A_CODE_POINT && key[keyIndex] == 0) {
-                // We found the key! Copy and return the value.
-                codePoint = getCodePointAndForwardPointer(dict, &index);
-                while (codePoint != NOT_A_CODE_POINT && outValueIndex < outValueSize) {
-                    outValue[outValueIndex++] = codePoint;
-                    codePoint = getCodePointAndForwardPointer(dict, &index);
-                }
-                // Finished copying. Break to go to the termination code.
-                break;
-            }
-            // We didn't find the key, skip the remainder of it and its value
-            while (codePoint != NOT_A_CODE_POINT) {
-                codePoint = getCodePointAndForwardPointer(dict, &index);
-            }
-            codePoint = getCodePointAndForwardPointer(dict, &index);
-            while (codePoint != NOT_A_CODE_POINT) {
-                codePoint = getCodePointAndForwardPointer(dict, &index);
-            }
-        }
-        // We couldn't find it - fall through and return an empty value.
-    }
-    // Put a terminator 0 if possible at all (always unless outValueSize is <= 0)
-    if (outValueIndex >= outValueSize) outValueIndex = outValueSize - 1;
-    if (outValueIndex >= 0) outValue[outValueIndex] = 0;
-}
-
-inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const int dictSize,
-        const char *const key) {
-    const int bufferSize = LARGEST_INT_DIGIT_COUNT;
-    int intBuffer[bufferSize];
-    char charBuffer[bufferSize];
-    BinaryFormat::readHeaderValue(dict, dictSize, key, intBuffer, bufferSize);
-    for (int i = 0; i < bufferSize; ++i) {
-        charBuffer[i] = intBuffer[i];
-    }
-    // If not a number, return S_INT_MIN
-    if (!isdigit(charBuffer[0])) return S_INT_MIN;
-    return atoi(charBuffer);
-}
-
-AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict,
-        int *pos) {
-    const int msb = dict[(*pos)++];
-    if (msb < 0x80) return msb;
-    return ((msb & 0x7F) << 8) | dict[(*pos)++];
-}
-
-inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict,
-        const int dictSize) {
-    const int headerValue = readHeaderValueInt(dict, dictSize,
-            "MULTIPLE_WORDS_DEMOTION_RATE");
-    if (headerValue == S_INT_MIN) {
-        return 1.0f;
-    }
-    if (headerValue <= 0) {
-        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
-    }
-    return 100.0f / static_cast<float>(headerValue);
-}
-
-inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict, int *pos) {
-    return dict[(*pos)++];
-}
-
-AK_FORCE_INLINE int BinaryFormat::getCodePointAndForwardPointer(const uint8_t *const dict,
-        int *pos) {
-    const int origin = *pos;
-    const int codePoint = dict[origin];
-    if (codePoint < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
-        if (codePoint == CHARACTER_ARRAY_TERMINATOR) {
-            *pos = origin + 1;
-            return NOT_A_CODE_POINT;
-        } else {
-            *pos = origin + 3;
-            const int char_1 = codePoint << 16;
-            const int char_2 = char_1 + (dict[origin + 1] << 8);
-            return char_2 + dict[origin + 2];
-        }
-    } else {
-        *pos = origin + 1;
-        return codePoint;
-    }
-}
-
-inline int BinaryFormat::readProbabilityWithoutMovingPointer(const uint8_t *const dict,
-        const int pos) {
-    return dict[pos];
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipOtherCharacters(const uint8_t *const dict, const int pos) {
-    int currentPos = pos;
-    int character = dict[currentPos++];
-    while (CHARACTER_ARRAY_TERMINATOR != character) {
-        if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
-            currentPos += MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE;
-        }
-        character = dict[currentPos++];
-    }
-    return currentPos;
-}
-
-static inline int attributeAddressSize(const uint8_t flags) {
-    static const int ATTRIBUTE_ADDRESS_SHIFT = 4;
-    return (flags & BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
-    /* Note: this is a value-dependant optimization of what may probably be
-       more readably written this way:
-       switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
-       case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
-       default: return 0;
-       }
-    */
-}
-
-static AK_FORCE_INLINE int skipExistingBigrams(const uint8_t *const dict, const int pos) {
-    int currentPos = pos;
-    uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
-    while (flags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT) {
-        currentPos += attributeAddressSize(flags);
-        flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
-    }
-    currentPos += attributeAddressSize(flags);
-    return currentPos;
-}
-
-static inline int childrenAddressSize(const uint8_t flags) {
-    static const int CHILDREN_ADDRESS_SHIFT = 6;
-    return (BinaryFormat::MASK_GROUP_ADDRESS_TYPE & flags) >> CHILDREN_ADDRESS_SHIFT;
-    /* See the note in attributeAddressSize. The same applies here */
-}
-
-static AK_FORCE_INLINE int shortcutByteSize(const uint8_t *const dict, const int pos) {
-    return (static_cast<int>(dict[pos] << 8)) + (dict[pos + 1]);
-}
-
-inline int BinaryFormat::skipChildrenPosition(const uint8_t flags, const int pos) {
-    return pos + childrenAddressSize(flags);
-}
-
-inline int BinaryFormat::skipProbability(const uint8_t flags, const int pos) {
-    return FLAG_IS_TERMINAL & flags ? pos + 1 : pos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    if (FLAG_HAS_SHORTCUT_TARGETS & flags) {
-        return pos + shortcutByteSize(dict, pos);
-    } else {
-        return pos;
-    }
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    if (FLAG_HAS_BIGRAMS & flags) {
-        return skipExistingBigrams(dict, pos);
-    } else {
-        return pos;
-    }
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint8_t flags,
-        const int pos) {
-    // This function skips all attributes: shortcuts and bigrams.
-    int newPos = pos;
-    newPos = skipShortcuts(dict, flags, newPos);
-    newPos = skipBigrams(dict, flags, newPos);
-    return newPos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict,
-        const uint8_t flags, const int pos) {
-    int currentPos = pos;
-    currentPos = skipChildrenPosition(flags, currentPos);
-    currentPos = skipAllAttributes(dict, flags, currentPos);
-    return currentPos;
-}
-
-AK_FORCE_INLINE int BinaryFormat::readChildrenPosition(const uint8_t *const dict,
-        const uint8_t flags, const int pos) {
-    int offset = 0;
-    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
-        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
-            offset = dict[pos];
-            break;
-        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
-            offset = dict[pos] << 8;
-            offset += dict[pos + 1];
-            break;
-        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
-            offset = dict[pos] << 16;
-            offset += dict[pos + 1] << 8;
-            offset += dict[pos + 2];
-            break;
-        default:
-            // If we come here, it means we asked for the children of a word with
-            // no children.
-            return -1;
-    }
-    return pos + offset;
-}
-
-inline bool BinaryFormat::hasChildrenInFlags(const uint8_t flags) {
-    return (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags));
-}
-
-AK_FORCE_INLINE int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *const dict,
-        const uint8_t flags, int *pos) {
-    int offset = 0;
-    const int origin = *pos;
-    switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
-            offset = dict[origin];
-            *pos = origin + 1;
-            break;
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
-            offset = dict[origin] << 8;
-            offset += dict[origin + 1];
-            *pos = origin + 2;
-            break;
-        case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
-            offset = dict[origin] << 16;
-            offset += dict[origin + 1] << 8;
-            offset += dict[origin + 2];
-            *pos = origin + 3;
-            break;
-    }
-    if (FLAG_ATTRIBUTE_OFFSET_NEGATIVE & flags) {
-        return origin - offset;
-    } else {
-        return origin + offset;
-    }
-}
-
-inline int BinaryFormat::getAttributeProbabilityFromFlags(const int flags) {
-    return flags & MASK_ATTRIBUTE_PROBABILITY;
-}
-
-// This function gets the byte position of the last chargroup of the exact matching word in the
-// dictionary. If no match is found, it returns NOT_VALID_WORD.
-AK_FORCE_INLINE int BinaryFormat::getTerminalPosition(const uint8_t *const root,
-        const int *const inWord, const int length, const bool forceLowerCaseSearch) {
-    int pos = 0;
-    int wordPos = 0;
-
-    while (true) {
-        // If we already traversed the tree further than the word is long, there means
-        // there was no match (or we would have found it).
-        if (wordPos >= length) return NOT_VALID_WORD;
-        int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
-        const int wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos];
-        while (true) {
-            // If there are no more character groups in this node, it means we could not
-            // find a matching character for this depth, therefore there is no match.
-            if (0 >= charGroupCount) return NOT_VALID_WORD;
-            const int charGroupPos = pos;
-            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-            int character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-            if (character == wChar) {
-                // This is the correct node. Only one character group may start with the same
-                // char within a node, so either we found our match in this node, or there is
-                // no match and we can return NOT_VALID_WORD. So we will check all the characters
-                // in this character group indeed does match.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-                    while (NOT_A_CODE_POINT != character) {
-                        ++wordPos;
-                        // If we shoot the length of the word we search for, or if we find a single
-                        // character that does not match, as explained above, it means the word is
-                        // not in the dictionary (by virtue of this chargroup being the only one to
-                        // match the word on the first character, but not matching the whole word).
-                        if (wordPos >= length) return NOT_VALID_WORD;
-                        if (inWord[wordPos] != character) return NOT_VALID_WORD;
-                        character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-                    }
-                }
-                // If we come here we know that so far, we do match. Either we are on a terminal
-                // and we match the length, in which case we found it, or we traverse children.
-                // If we don't match the length AND don't have children, then a word in the
-                // dictionary fully matches a prefix of the searched word but not the full word.
-                ++wordPos;
-                if (FLAG_IS_TERMINAL & flags) {
-                    if (wordPos == length) {
-                        return charGroupPos;
-                    }
-                    pos = BinaryFormat::skipProbability(FLAG_IS_TERMINAL, pos);
-                }
-                if (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS == (MASK_GROUP_ADDRESS_TYPE & flags)) {
-                    return NOT_VALID_WORD;
-                }
-                // We have children and we are still shorter than the word we are searching for, so
-                // we need to traverse children. Put the pointer on the children position, and
-                // break
-                pos = BinaryFormat::readChildrenPosition(root, flags, pos);
-                break;
-            } else {
-                // This chargroup does not match, so skip the remaining part and go to the next.
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    pos = BinaryFormat::skipOtherCharacters(root, pos);
-                }
-                pos = BinaryFormat::skipProbability(flags, pos);
-                pos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
-            }
-            --charGroupCount;
-        }
-    }
-}
-
-// This function searches for a terminal in the dictionary by its address.
-// Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
-// it is possible to check for this with advantageous complexity. For each node, we search
-// for groups with children and compare the children address with the address we look for.
-// When we shoot the address we look for, it means the word we look for is in the children
-// of the previous group. The only tricky part is the fact that if we arrive at the end of a
-// node with the last group's children address still less than what we are searching for, we
-// must descend the last group's children (for example, if the word we are searching for starts
-// with a z, it's the last group of the root node, so all children addresses will be smaller
-// than the address we look for, and we have to descend the z node).
-/* Parameters :
- * root: the dictionary buffer
- * address: the byte position of the last chargroup of the word we are searching for (this is
- *   what is stored as the "bigram address" in each bigram)
- * outword: an array to write the found word, with MAX_WORD_LENGTH size.
- * outUnigramProbability: a pointer to an int to write the probability into.
- * Return value : the length of the word, of 0 if the word was not found.
- */
-AK_FORCE_INLINE int BinaryFormat::getWordAtAddress(const uint8_t *const root, const int address,
-        const int maxDepth, int *outWord, int *outUnigramProbability) {
-    int pos = 0;
-    int wordPos = 0;
-
-    // One iteration of the outer loop iterates through nodes. As stated above, we will only
-    // traverse nodes that are actually a part of the terminal we are searching, so each time
-    // we enter this loop we are one depth level further than last time.
-    // The only reason we count nodes is because we want to reduce the probability of infinite
-    // looping in case there is a bug. Since we know there is an upper bound to the depth we are
-    // supposed to traverse, it does not hurt to count iterations.
-    for (int loopCount = maxDepth; loopCount > 0; --loopCount) {
-        int lastCandidateGroupPos = 0;
-        // Let's loop through char groups in this node searching for either the terminal
-        // or one of its ascendants.
-        for (int charGroupCount = getGroupCountAndForwardPointer(root, &pos); charGroupCount > 0;
-                 --charGroupCount) {
-            const int startPos = pos;
-            const uint8_t flags = getFlagsAndForwardPointer(root, &pos);
-            const int character = getCodePointAndForwardPointer(root, &pos);
-            if (address == startPos) {
-                // We found the address. Copy the rest of the word in the buffer and return
-                // the length.
-                outWord[wordPos] = character;
-                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                    int nextChar = getCodePointAndForwardPointer(root, &pos);
-                    // We count chars in order to avoid infinite loops if the file is broken or
-                    // if there is some other bug
-                    int charCount = maxDepth;
-                    while (NOT_A_CODE_POINT != nextChar && --charCount > 0) {
-                        outWord[++wordPos] = nextChar;
-                        nextChar = getCodePointAndForwardPointer(root, &pos);
-                    }
-                }
-                *outUnigramProbability = readProbabilityWithoutMovingPointer(root, pos);
-                return ++wordPos;
-            }
-            // We need to skip past this char group, so skip any remaining chars after the
-            // first and possibly the probability.
-            if (FLAG_HAS_MULTIPLE_CHARS & flags) {
-                pos = skipOtherCharacters(root, pos);
-            }
-            pos = skipProbability(flags, pos);
-
-            // The fact that this group has children is very important. Since we already know
-            // that this group does not match, if it has no children we know it is irrelevant
-            // to what we are searching for.
-            const bool hasChildren = (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS !=
-                    (MASK_GROUP_ADDRESS_TYPE & flags));
-            // We will write in `found' whether we have passed the children address we are
-            // searching for. For example if we search for "beer", the children of b are less
-            // than the address we are searching for and the children of c are greater. When we
-            // come here for c, we realize this is too big, and that we should descend b.
-            bool found;
-            if (hasChildren) {
-                // Here comes the tricky part. First, read the children position.
-                const int childrenPos = readChildrenPosition(root, flags, pos);
-                if (childrenPos > address) {
-                    // If the children pos is greater than address, it means the previous chargroup,
-                    // which address is stored in lastCandidateGroupPos, was the right one.
-                    found = true;
-                } else if (1 >= charGroupCount) {
-                    // However if we are on the LAST group of this node, and we have NOT shot the
-                    // address we should descend THIS node. So we trick the lastCandidateGroupPos
-                    // so that we will descend this node, not the previous one.
-                    lastCandidateGroupPos = startPos;
-                    found = true;
-                } else {
-                    // Else, we should continue looking.
-                    found = false;
-                }
-            } else {
-                // Even if we don't have children here, we could still be on the last group of this
-                // node. If this is the case, we should descend the last group that had children,
-                // and their address is already in lastCandidateGroup.
-                found = (1 >= charGroupCount);
-            }
-
-            if (found) {
-                // Okay, we found the group we should descend. Its address is in
-                // the lastCandidateGroupPos variable, so we just re-read it.
-                if (0 != lastCandidateGroupPos) {
-                    const uint8_t lastFlags =
-                            getFlagsAndForwardPointer(root, &lastCandidateGroupPos);
-                    const int lastChar =
-                            getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                    // We copy all the characters in this group to the buffer
-                    outWord[wordPos] = lastChar;
-                    if (FLAG_HAS_MULTIPLE_CHARS & lastFlags) {
-                        int nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                        int charCount = maxDepth;
-                        while (-1 != nextChar && --charCount > 0) {
-                            outWord[++wordPos] = nextChar;
-                            nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
-                        }
-                    }
-                    ++wordPos;
-                    // Now we only need to branch to the children address. Skip the probability if
-                    // it's there, read pos, and break to resume the search at pos.
-                    lastCandidateGroupPos = skipProbability(lastFlags, lastCandidateGroupPos);
-                    pos = readChildrenPosition(root, lastFlags, lastCandidateGroupPos);
-                    break;
-                } else {
-                    // Here is a little tricky part: we come here if we found out that all children
-                    // addresses in this group are bigger than the address we are searching for.
-                    // Should we conclude the word is not in the dictionary? No! It could still be
-                    // one of the remaining chargroups in this node, so we have to keep looking in
-                    // this node until we find it (or we realize it's not there either, in which
-                    // case it's actually not in the dictionary). Pass the end of this group, ready
-                    // to start the next one.
-                    pos = skipChildrenPosAndAttributes(root, flags, pos);
-                }
-            } else {
-                // If we did not find it, we should record the last children address for the next
-                // iteration.
-                if (hasChildren) lastCandidateGroupPos = startPos;
-                // Now skip the end of this group (children pos and the attributes if any) so that
-                // our pos is after the end of this char group, at the start of the next one.
-                pos = skipChildrenPosAndAttributes(root, flags, pos);
-            }
-
-        }
-    }
-    // If we have looked through all the chargroups and found no match, the address is
-    // not the address of a terminal in this dictionary.
-    return 0;
-}
-
-static inline int backoff(const int unigramProbability) {
-    return unigramProbability;
-    // For some reason, applying the backoff weight gives bad results in tests. To apply the
-    // backoff weight, we divide the probability by 2, which in our storing format means
-    // decreasing the score by 8.
-    // TODO: figure out what's wrong with this.
-    // return unigramProbability > 8 ? unigramProbability - 8 : (0 == unigramProbability ? 0 : 8);
-}
-
-inline int BinaryFormat::computeProbabilityForBigram(
-        const int unigramProbability, const int bigramProbability) {
-    // We divide the range [unigramProbability..255] in 16.5 steps - in other words, we want the
-    // unigram probability to be the median value of the 17th step from the top. A value of
-    // 0 for the bigram probability represents the middle of the 16th step from the top,
-    // while a value of 15 represents the middle of the top step.
-    // See makedict.BinaryDictInputOutput for details.
-    const float stepSize = static_cast<float>(MAX_PROBABILITY - unigramProbability)
-            / (1.5f + MAX_BIGRAM_ENCODED_PROBABILITY);
-    return unigramProbability
-            + static_cast<int>(static_cast<float>(bigramProbability + 1) * stepSize);
-}
-
-// This returns a probability in log space.
-inline int BinaryFormat::getProbability(const int position, const std::map<int, int> *bigramMap,
-        const uint8_t *bigramFilter, const int unigramProbability) {
-    if (!bigramMap || !bigramFilter) return backoff(unigramProbability);
-    if (!isInFilter(bigramFilter, position)) return backoff(unigramProbability);
-    const std::map<int, int>::const_iterator bigramProbabilityIt = bigramMap->find(position);
-    if (bigramProbabilityIt != bigramMap->end()) {
-        const int bigramProbability = bigramProbabilityIt->second;
-        return computeProbabilityForBigram(unigramProbability, bigramProbability);
-    }
-    return backoff(unigramProbability);
-}
-
-// This returns a probability in log space.
-inline int BinaryFormat::getBigramProbabilityFromHashMap(const int position,
-        const hash_map_compat<int, int> *bigramMap, const int unigramProbability) {
-    if (!bigramMap) return backoff(unigramProbability);
-    const hash_map_compat<int, int>::const_iterator bigramProbabilityIt = bigramMap->find(position);
-    if (bigramProbabilityIt != bigramMap->end()) {
-        const int bigramProbability = bigramProbabilityIt->second;
-        return computeProbabilityForBigram(unigramProbability, bigramProbability);
-    }
-    return backoff(unigramProbability);
-}
-
-AK_FORCE_INLINE void BinaryFormat::fillBigramProbabilityToHashMap(
-        const uint8_t *const root, int position, hash_map_compat<int, int> *bigramMap) {
-    position = getBigramListPositionForWordPosition(root, position);
-    if (0 == position) return;
-
-    uint8_t bigramFlags;
-    do {
-        bigramFlags = getFlagsAndForwardPointer(root, &position);
-        const int probability = MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
-        const int bigramPos = getAttributeAddressAndForwardPointer(root, bigramFlags,
-                &position);
-        (*bigramMap)[bigramPos] = probability;
-    } while (FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
-}
-
-AK_FORCE_INLINE int BinaryFormat::getBigramProbability(const uint8_t *const root, int position,
-        const int nextPosition, const int unigramProbability) {
-    position = getBigramListPositionForWordPosition(root, position);
-    if (0 == position) return backoff(unigramProbability);
-
-    uint8_t bigramFlags;
-    do {
-        bigramFlags = getFlagsAndForwardPointer(root, &position);
-        const int bigramPos = getAttributeAddressAndForwardPointer(
-                root, bigramFlags, &position);
-        if (bigramPos == nextPosition) {
-            const int bigramProbability = MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
-            return computeProbabilityForBigram(unigramProbability, bigramProbability);
-        }
-    } while (FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
-    return backoff(unigramProbability);
-}
-
-// Returns a pointer to the start of the bigram list.
-AK_FORCE_INLINE int BinaryFormat::getBigramListPositionForWordPosition(
-        const uint8_t *const root, int position) {
-    if (NOT_VALID_WORD == position) return 0;
-    const uint8_t flags = getFlagsAndForwardPointer(root, &position);
-    if (!(flags & FLAG_HAS_BIGRAMS)) return 0;
-    if (flags & FLAG_HAS_MULTIPLE_CHARS) {
-        position = skipOtherCharacters(root, position);
-    } else {
-        getCodePointAndForwardPointer(root, &position);
-    }
-    position = skipProbability(flags, position);
-    position = skipChildrenPosition(flags, position);
-    position = skipShortcuts(root, flags, position);
-    return position;
-}
-
-} // namespace latinime
-#endif // LATINIME_BINARY_FORMAT_H
diff --git a/native/jni/src/bloom_filter.h b/native/jni/src/bloom_filter.h
deleted file mode 100644
index bcce1f7..0000000
--- a/native/jni/src/bloom_filter.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_BLOOM_FILTER_H
-#define LATINIME_BLOOM_FILTER_H
-
-#include <stdint.h>
-
-#include "defines.h"
-
-namespace latinime {
-
-// TODO: uint32_t position
-static inline void setInFilter(uint8_t *filter, const int32_t position) {
-    const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
-    filter[bucket >> 3] |= static_cast<uint8_t>(1 << (bucket & 0x7));
-}
-
-// TODO: uint32_t position
-static inline bool isInFilter(const uint8_t *filter, const int32_t position) {
-    const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
-    return filter[bucket >> 3] & static_cast<uint8_t>(1 << (bucket & 0x7));
-}
-} // namespace latinime
-#endif // LATINIME_BLOOM_FILTER_H
diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h
deleted file mode 100644
index b429f40..0000000
--- a/native/jni/src/char_utils.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_CHAR_UTILS_H
-#define LATINIME_CHAR_UTILS_H
-
-#include <cctype>
-
-#include "defines.h"
-
-namespace latinime {
-
-inline static bool isAsciiUpper(int c) {
-    // Note: isupper(...) reports false positives for some Cyrillic characters, causing them to
-    // be incorrectly lower-cased using toAsciiLower(...) rather than latin_tolower(...).
-    return (c >= 'A' && c <= 'Z');
-}
-
-inline static int toAsciiLower(int c) {
-    return c - 'A' + 'a';
-}
-
-inline static bool isAscii(int c) {
-    return isascii(c) != 0;
-}
-
-unsigned short latin_tolower(const unsigned short c);
-
-/**
- * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters.  If c is in range, BASE_CHARS[c] == c
- * if c is not a combined character, or the base character if it
- * is combined.
- */
-static const int BASE_CHARS_SIZE = 0x0500;
-extern const unsigned short BASE_CHARS[BASE_CHARS_SIZE];
-
-inline static int toBaseCodePoint(int c) {
-    if (c < BASE_CHARS_SIZE) {
-        return static_cast<int>(BASE_CHARS[c]);
-    }
-    return c;
-}
-
-AK_FORCE_INLINE static int toLowerCase(const int c) {
-    if (isAsciiUpper(c)) {
-        return toAsciiLower(c);
-    }
-    if (isAscii(c)) {
-        return c;
-    }
-    return static_cast<int>(latin_tolower(static_cast<unsigned short>(c)));
-}
-
-AK_FORCE_INLINE static int toBaseLowerCase(const int c) {
-    return toLowerCase(toBaseCodePoint(c));
-}
-
-inline static bool isIntentionalOmissionCodePoint(const int codePoint) {
-    // TODO: Do not hardcode here
-    return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
-}
-
-inline static int getCodePointCount(const int arraySize, const int *const codePoints) {
-    int size = 0;
-    for (; size < arraySize; ++size) {
-        if (codePoints[size] == '\0') {
-            break;
-        }
-    }
-    return size;
-}
-
-} // namespace latinime
-#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
deleted file mode 100644
index 61bf3f6..0000000
--- a/native/jni/src/correction.cpp
+++ /dev/null
@@ -1,979 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "LatinIME: correction.cpp"
-
-#include <cmath>
-
-#include "char_utils.h"
-#include "correction.h"
-#include "defines.h"
-#include "proximity_info_state.h"
-#include "suggest_utils.h"
-#include "suggest/policyimpl/utils/edit_distance.h"
-#include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h"
-
-namespace latinime {
-
-class ProximityInfo;
-
-/////////////////////////////
-// edit distance funcitons //
-/////////////////////////////
-
-inline static void initEditDistance(int *editDistanceTable) {
-    for (int i = 0; i <= MAX_WORD_LENGTH; ++i) {
-        editDistanceTable[i] = i;
-    }
-}
-
-inline static void dumpEditDistance10ForDebug(int *editDistanceTable,
-        const int editDistanceTableWidth, const int outputLength) {
-    if (DEBUG_DICT) {
-        AKLOGI("EditDistanceTable");
-        for (int i = 0; i <= 10; ++i) {
-            int c[11];
-            for (int j = 0; j <= 10; ++j) {
-                if (j < editDistanceTableWidth + 1 && i < outputLength + 1) {
-                    c[j] = (editDistanceTable + i * (editDistanceTableWidth + 1))[j];
-                } else {
-                    c[j] = -1;
-                }
-            }
-            AKLOGI("[ %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d ]",
-                    c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]);
-            (void)c; // To suppress compiler warning
-        }
-    }
-}
-
-inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth,
-        const int outputLength, const int inputSize) {
-    if (DEBUG_EDIT_DISTANCE) {
-        AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength);
-    }
-    return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize];
-}
-
-////////////////
-// Correction //
-////////////////
-
-void Correction::resetCorrection() {
-    mTotalTraverseCount = 0;
-}
-
-void Correction::initCorrection(const ProximityInfo *pi, const int inputSize, const int maxDepth) {
-    mProximityInfo = pi;
-    mInputSize = inputSize;
-    mMaxDepth = maxDepth;
-    mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2;
-    // TODO: This is not supposed to be required.  Check what's going wrong with
-    // editDistance[0 ~ MAX_WORD_LENGTH]
-    initEditDistance(mEditDistanceTable);
-}
-
-void Correction::initCorrectionState(
-        const int rootPos, const int childCount, const bool traverseAll) {
-    latinime::initCorrectionState(mCorrectionStates, rootPos, childCount, traverseAll);
-    // TODO: remove
-    mCorrectionStates[0].mTransposedPos = mTransposedPos;
-    mCorrectionStates[0].mExcessivePos = mExcessivePos;
-    mCorrectionStates[0].mSkipPos = mSkipPos;
-}
-
-void Correction::setCorrectionParams(const int skipPos, const int excessivePos,
-        const int transposedPos, const int spaceProximityPos, const int missingSpacePos,
-        const bool useFullEditDistance, const bool doAutoCompletion, const int maxErrors) {
-    // TODO: remove
-    mTransposedPos = transposedPos;
-    mExcessivePos = excessivePos;
-    mSkipPos = skipPos;
-    // TODO: remove
-    mCorrectionStates[0].mTransposedPos = transposedPos;
-    mCorrectionStates[0].mExcessivePos = excessivePos;
-    mCorrectionStates[0].mSkipPos = skipPos;
-
-    mSpaceProximityPos = spaceProximityPos;
-    mMissingSpacePos = missingSpacePos;
-    mUseFullEditDistance = useFullEditDistance;
-    mDoAutoCompletion = doAutoCompletion;
-    mMaxErrors = maxErrors;
-}
-
-void Correction::checkState() const {
-    if (DEBUG_DICT) {
-        int inputCount = 0;
-        if (mSkipPos >= 0) ++inputCount;
-        if (mExcessivePos >= 0) ++inputCount;
-        if (mTransposedPos >= 0) ++inputCount;
-    }
-}
-
-bool Correction::sameAsTyped() const {
-    return mProximityInfoState.sameAsTyped(mWord, mOutputIndex);
-}
-
-int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
-        const int wordCount, const bool isSpaceProximity, const int *word) const {
-    return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray,
-            wordCount, this, isSpaceProximity, word);
-}
-
-int Correction::getFinalProbability(const int probability, int **word, int *wordLength) {
-    return getFinalProbabilityInternal(probability, word, wordLength, mInputSize);
-}
-
-int Correction::getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength,
-        const int inputSize) {
-    return getFinalProbabilityInternal(probability, word, wordLength, inputSize);
-}
-
-bool Correction::initProcessState(const int outputIndex) {
-    if (mCorrectionStates[outputIndex].mChildCount <= 0) {
-        return false;
-    }
-    mOutputIndex = outputIndex;
-    --(mCorrectionStates[outputIndex].mChildCount);
-    mInputIndex = mCorrectionStates[outputIndex].mInputIndex;
-    mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes;
-
-    mEquivalentCharCount = mCorrectionStates[outputIndex].mEquivalentCharCount;
-    mProximityCount = mCorrectionStates[outputIndex].mProximityCount;
-    mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount;
-    mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount;
-    mSkippedCount = mCorrectionStates[outputIndex].mSkippedCount;
-    mLastCharExceeded = mCorrectionStates[outputIndex].mLastCharExceeded;
-
-    mTransposedPos = mCorrectionStates[outputIndex].mTransposedPos;
-    mExcessivePos = mCorrectionStates[outputIndex].mExcessivePos;
-    mSkipPos = mCorrectionStates[outputIndex].mSkipPos;
-
-    mMatching = false;
-    mProximityMatching = false;
-    mAdditionalProximityMatching = false;
-    mTransposing = false;
-    mExceeding = false;
-    mSkipping = false;
-
-    return true;
-}
-
-int Correction::goDownTree(const int parentIndex, const int childCount, const int firstChildPos) {
-    mCorrectionStates[mOutputIndex].mParentIndex = parentIndex;
-    mCorrectionStates[mOutputIndex].mChildCount = childCount;
-    mCorrectionStates[mOutputIndex].mSiblingPos = firstChildPos;
-    return mOutputIndex;
-}
-
-// TODO: remove
-int Correction::getInputIndex() const {
-    return mInputIndex;
-}
-
-bool Correction::needsToPrune() const {
-    // TODO: use edit distance here
-    return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance
-            // Allow one char longer word for missing character
-            || (!mDoAutoCompletion && (mOutputIndex > mInputSize));
-}
-
-inline static bool isEquivalentChar(ProximityType type) {
-    return type == MATCH_CHAR;
-}
-
-inline static bool isProximityCharOrEquivalentChar(ProximityType type) {
-    return type == MATCH_CHAR || type == PROXIMITY_CHAR;
-}
-
-Correction::CorrectionType Correction::processCharAndCalcState(const int c, const bool isTerminal) {
-    const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount);
-    if (correctionCount > mMaxErrors) {
-        return processUnrelatedCorrectionType();
-    }
-
-    // TODO: Change the limit if we'll allow two or more corrections
-    const bool noCorrectionsHappenedSoFar = correctionCount == 0;
-    const bool canTryCorrection = noCorrectionsHappenedSoFar;
-    int proximityIndex = 0;
-    mDistances[mOutputIndex] = NOT_A_DISTANCE;
-
-    // Skip checking this node
-    if (mNeedsToTraverseAllNodes || isSingleQuote(c)) {
-        bool incremented = false;
-        if (mLastCharExceeded && mInputIndex == mInputSize - 1) {
-            // TODO: Do not check the proximity if EditDistance exceeds the threshold
-            const ProximityType matchId = mProximityInfoState.getProximityType(
-                    mInputIndex, c, true, &proximityIndex);
-            if (isEquivalentChar(matchId)) {
-                mLastCharExceeded = false;
-                --mExcessiveCount;
-                mDistances[mOutputIndex] =
-                        mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
-            } else if (matchId == PROXIMITY_CHAR) {
-                mLastCharExceeded = false;
-                --mExcessiveCount;
-                ++mProximityCount;
-                mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(
-                        mInputIndex, proximityIndex);
-            }
-            if (!isSingleQuote(c)) {
-                incrementInputIndex();
-                incremented = true;
-            }
-        }
-        return processSkipChar(c, isTerminal, incremented);
-    }
-
-    // Check possible corrections.
-    if (mExcessivePos >= 0) {
-        if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) {
-            mExcessivePos = mOutputIndex;
-        }
-        if (mExcessivePos < mInputSize - 1) {
-            mExceeding = mExcessivePos == mInputIndex && canTryCorrection;
-        }
-    }
-
-    if (mSkipPos >= 0) {
-        if (mSkippedCount == 0 && mSkipPos < mOutputIndex) {
-            if (DEBUG_DICT) {
-                // TODO: Enable this assertion.
-                //ASSERT(mSkipPos == mOutputIndex - 1);
-            }
-            mSkipPos = mOutputIndex;
-        }
-        mSkipping = mSkipPos == mOutputIndex && canTryCorrection;
-    }
-
-    if (mTransposedPos >= 0) {
-        if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) {
-            mTransposedPos = mOutputIndex;
-        }
-        if (mTransposedPos < mInputSize - 1) {
-            mTransposing = mInputIndex == mTransposedPos && canTryCorrection;
-        }
-    }
-
-    bool secondTransposing = false;
-    if (mTransposedCount % 2 == 1) {
-        if (isEquivalentChar(mProximityInfoState.getProximityType(
-                mInputIndex - 1, c, false))) {
-            ++mTransposedCount;
-            secondTransposing = true;
-        } else if (mCorrectionStates[mOutputIndex].mExceeding) {
-            --mTransposedCount;
-            ++mExcessiveCount;
-            --mExcessivePos;
-            incrementInputIndex();
-        } else {
-            --mTransposedCount;
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                DUMP_WORD(mWord, mOutputIndex);
-                AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                        mTransposedCount, mExcessiveCount, c);
-            }
-            return processUnrelatedCorrectionType();
-        }
-    }
-
-    // TODO: Change the limit if we'll allow two or more proximity chars with corrections
-    // Work around: When the mMaxErrors is 1, we only allow just one error
-    // including proximity correction.
-    const bool checkProximityChars = (mMaxErrors > 1)
-            ? (noCorrectionsHappenedSoFar || mProximityCount == 0)
-            : (noCorrectionsHappenedSoFar && mProximityCount == 0);
-
-    ProximityType matchedProximityCharId = secondTransposing
-            ? MATCH_CHAR
-            : mProximityInfoState.getProximityType(
-                    mInputIndex, c, checkProximityChars, &proximityIndex);
-
-    if (SUBSTITUTION_CHAR == matchedProximityCharId
-            || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
-        if (canTryCorrection && mOutputIndex > 0
-                && mCorrectionStates[mOutputIndex].mProximityMatching
-                && mCorrectionStates[mOutputIndex].mExceeding
-                && isEquivalentChar(mProximityInfoState.getProximityType(
-                        mInputIndex, mWord[mOutputIndex - 1], false))) {
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]);
-            }
-            // Conversion p->e
-            // Example:
-            // wearth ->    earth
-            // px     -> (E)mmmmm
-            ++mExcessiveCount;
-            --mProximityCount;
-            mExcessivePos = mOutputIndex - 1;
-            ++mInputIndex;
-            // Here, we are doing something equivalent to matchedProximityCharId,
-            // but we already know that "excessive char correction" just happened
-            // so that we just need to check "mProximityCount == 0".
-            matchedProximityCharId = mProximityInfoState.getProximityType(
-                    mInputIndex, c, mProximityCount == 0, &proximityIndex);
-        }
-    }
-
-    if (SUBSTITUTION_CHAR == matchedProximityCharId
-            || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
-        if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
-            mAdditionalProximityMatching = true;
-        }
-        // TODO: Optimize
-        // As the current char turned out to be an unrelated char,
-        // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex]
-        // here refers to the previous state.
-        if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0
-                && !mCorrectionStates[mOutputIndex].mTransposing
-                && mCorrectionStates[mOutputIndex - 1].mTransposing
-                && isEquivalentChar(mProximityInfoState.getProximityType(
-                        mInputIndex, mWord[mOutputIndex - 1], false))
-                && isEquivalentChar(
-                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
-            // Conversion t->e
-            // Example:
-            // occaisional -> occa   sional
-            // mmmmttx     -> mmmm(E)mmmmmm
-            mTransposedCount -= 2;
-            ++mExcessiveCount;
-            ++mInputIndex;
-        } else if (mOutputIndex > 0 && mInputIndex > 0 && mTransposedCount > 0
-                && !mCorrectionStates[mOutputIndex].mTransposing
-                && mCorrectionStates[mOutputIndex - 1].mTransposing
-                && isEquivalentChar(
-                        mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) {
-            // Conversion t->s
-            // Example:
-            // chcolate -> chocolate
-            // mmttx    -> mmsmmmmmm
-            mTransposedCount -= 2;
-            ++mSkippedCount;
-            --mInputIndex;
-        } else if (canTryCorrection && mInputIndex > 0
-                && mCorrectionStates[mOutputIndex].mProximityMatching
-                && mCorrectionStates[mOutputIndex].mSkipping
-                && isEquivalentChar(
-                        mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) {
-            // Conversion p->s
-            // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of
-            // proximity chars of "s", but it should rather be handled as a skipped char.
-            ++mSkippedCount;
-            --mProximityCount;
-            return processSkipChar(c, isTerminal, false);
-        } else if (mInputIndex - 1 < mInputSize
-                && mSkippedCount > 0
-                && mCorrectionStates[mOutputIndex].mSkipping
-                && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
-                && isProximityCharOrEquivalentChar(
-                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
-            // Conversion s->a
-            incrementInputIndex();
-            --mSkippedCount;
-            mProximityMatching = true;
-            ++mProximityCount;
-            mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
-        } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize
-                && isEquivalentChar(
-                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
-            // 1.2. Excessive or transpose correction
-            if (mTransposing) {
-                ++mTransposedCount;
-            } else {
-                ++mExcessiveCount;
-                incrementInputIndex();
-            }
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                DUMP_WORD(mWord, mOutputIndex);
-                if (mTransposing) {
-                    AKLOGI("TRANSPOSE: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                            mTransposedCount, mExcessiveCount, c);
-                } else {
-                    AKLOGI("EXCEED: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                            mTransposedCount, mExcessiveCount, c);
-                }
-            }
-        } else if (mSkipping) {
-            // 3. Skip correction
-            ++mSkippedCount;
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                        mTransposedCount, mExcessiveCount, c);
-            }
-            return processSkipChar(c, isTerminal, false);
-        } else if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
-            // As a last resort, use additional proximity characters
-            mProximityMatching = true;
-            ++mProximityCount;
-            mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                        mTransposedCount, mExcessiveCount, c);
-            }
-        } else {
-            if (DEBUG_CORRECTION
-                    && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                    && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                            || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-                DUMP_WORD(mWord, mOutputIndex);
-                AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                        mTransposedCount, mExcessiveCount, c);
-            }
-            return processUnrelatedCorrectionType();
-        }
-    } else if (secondTransposing) {
-        // If inputIndex is greater than mInputSize, that means there is no
-        // proximity chars. So, we don't need to check proximity.
-        mMatching = true;
-    } else if (isEquivalentChar(matchedProximityCharId)) {
-        mMatching = true;
-        ++mEquivalentCharCount;
-        mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
-    } else if (PROXIMITY_CHAR == matchedProximityCharId) {
-        mProximityMatching = true;
-        ++mProximityCount;
-        mDistances[mOutputIndex] =
-                mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex);
-        if (DEBUG_CORRECTION
-                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0
-                        || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-            AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                    mTransposedCount, mExcessiveCount, c);
-        }
-    }
-
-    addCharToCurrentWord(c);
-
-    // 4. Last char excessive correction
-    mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0
-            && mProximityCount == 0 && (mInputIndex == mInputSize - 2);
-    const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded;
-    if (mLastCharExceeded) {
-        ++mExcessiveCount;
-    }
-
-    // Start traversing all nodes after the index exceeds the user typed length
-    if (isSameAsUserTypedLength) {
-        startToTraverseAllNodes();
-    }
-
-    const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar =
-            mExceeding && mInputIndex == mInputSize - 2;
-
-    // Finally, we are ready to go to the next character, the next "virtual node".
-    // We should advance the input index.
-    // We do this in this branch of the 'if traverseAllNodes' because we are still matching
-    // characters to input; the other branch is not matching them but searching for
-    // completions, this is why it does not have to do it.
-    incrementInputIndex();
-    // Also, the next char is one "virtual node" depth more than this char.
-    incrementOutputIndex();
-
-    if ((needsToTryOnTerminalForTheLastPossibleExcessiveChar
-            || isSameAsUserTypedLength) && isTerminal) {
-        mTerminalInputIndex = mInputIndex - 1;
-        mTerminalOutputIndex = mOutputIndex - 1;
-        if (DEBUG_CORRECTION
-                && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
-                && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) {
-            DUMP_WORD(mWord, mOutputIndex);
-            AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount,
-                    mTransposedCount, mExcessiveCount, c);
-        }
-        return ON_TERMINAL;
-    } else {
-        mTerminalInputIndex = mInputIndex - 1;
-        mTerminalOutputIndex = mOutputIndex - 1;
-        return NOT_ON_TERMINAL;
-    }
-}
-
-inline static int getQuoteCount(const int *word, const int length) {
-    int quoteCount = 0;
-    for (int i = 0; i < length; ++i) {
-        if (word[i] == KEYCODE_SINGLE_QUOTE) {
-            ++quoteCount;
-        }
-    }
-    return quoteCount;
-}
-
-inline static bool isUpperCase(unsigned short c) {
-    return isAsciiUpper(toBaseCodePoint(c));
-}
-
-//////////////////////
-// RankingAlgorithm //
-//////////////////////
-
-/* static */ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex,
-        const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction,
-        const int inputSize) {
-    const int excessivePos = correction->getExcessivePos();
-    const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
-    const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
-    const ProximityInfoState *proximityInfoState = &correction->mProximityInfoState;
-    const int skippedCount = correction->mSkippedCount;
-    const int transposedCount = correction->mTransposedCount / 2;
-    const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2;
-    const int proximityMatchedCount = correction->mProximityCount;
-    const bool lastCharExceeded = correction->mLastCharExceeded;
-    const bool useFullEditDistance = correction->mUseFullEditDistance;
-    const int outputLength = outputIndex + 1;
-    if (skippedCount >= inputSize || inputSize == 0) {
-        return -1;
-    }
-
-    // TODO: find more robust way
-    bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2)
-            : (inputSize == inputIndex + 1);
-
-    // TODO: use mExcessiveCount
-    const int matchCount = inputSize - correction->mProximityCount - excessiveCount;
-
-    const int *word = correction->mWord;
-    const bool skipped = skippedCount > 0;
-
-    const int quoteDiffCount = max(0, getQuoteCount(word, outputLength)
-            - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize));
-
-    // TODO: Calculate edit distance for transposed and excessive
-    int ed = 0;
-    if (DEBUG_DICT_FULL) {
-        dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength);
-    }
-    int adjustedProximityMatchedCount = proximityMatchedCount;
-
-    int finalFreq = freq;
-
-    if (DEBUG_CORRECTION_FREQ
-            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
-        AKLOGI("FinalFreq0: %d", finalFreq);
-    }
-    // TODO: Optimize this.
-    if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) {
-        ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength,
-                inputSize) - transposedCount;
-
-        const int matchWeight = powerIntCapped(typedLetterMultiplier,
-                max(inputSize, outputLength) - ed);
-        multiplyIntCapped(matchWeight, &finalFreq);
-
-        // TODO: Demote further if there are two or more excessive chars with longer user input?
-        if (inputSize > outputLength) {
-            multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq);
-        }
-
-        ed = max(0, ed - quoteDiffCount);
-        adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)),
-                proximityMatchedCount);
-        if (transposedCount <= 0) {
-            if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) {
-                // Promote a word with just one skipped or excessive char
-                if (sameLength) {
-                    multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE
-                            + WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER * outputLength,
-                            &finalFreq);
-                } else {
-                    multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-                }
-            } else if (ed == 0) {
-                multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-                sameLength = true;
-            }
-        }
-    } else {
-        const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
-        multiplyIntCapped(matchWeight, &finalFreq);
-    }
-
-    if (proximityInfoState->getProximityType(0, word[0], true) == SUBSTITUTION_CHAR) {
-        multiplyRate(FIRST_CHAR_DIFFERENT_DEMOTION_RATE, &finalFreq);
-    }
-
-    ///////////////////////////////////////////////
-    // Promotion and Demotion for each correction
-
-    // Demotion for a word with missing character
-    if (skipped) {
-        const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
-                * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
-                / (10 * inputSize
-                        - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10);
-        if (DEBUG_DICT_FULL) {
-            AKLOGI("Demotion rate for missing character is %d.", demotionRate);
-        }
-        multiplyRate(demotionRate, &finalFreq);
-    }
-
-    // Demotion for a word with transposed character
-    if (transposedCount > 0) multiplyRate(
-            WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
-
-    // Demotion for a word with excessive character
-    if (excessiveCount > 0) {
-        multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq);
-        if (!lastCharExceeded && !proximityInfoState->existsAdjacentProximityChars(excessivePos)) {
-            if (DEBUG_DICT_FULL) {
-                AKLOGI("Double excessive demotion");
-            }
-            // If an excessive character is not adjacent to the left char or the right char,
-            // we will demote this word.
-            multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq);
-        }
-    }
-
-    int additionalProximityCount = 0;
-    // Demote additional proximity characters
-    for (int i = 0; i < outputLength; ++i) {
-        const int squaredDistance = correction->mDistances[i];
-        if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) {
-            ++additionalProximityCount;
-        }
-    }
-
-    const bool performTouchPositionCorrection =
-            CALIBRATE_SCORE_BY_TOUCH_COORDINATES
-                    && proximityInfoState->touchPositionCorrectionEnabled()
-                    && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0
-                    && additionalProximityCount == 0;
-
-    // Score calibration by touch coordinates is being done only for pure-fat finger typing error
-    // cases.
-    // TODO: Remove this constraint.
-    if (performTouchPositionCorrection) {
-        for (int i = 0; i < outputLength; ++i) {
-            const int squaredDistance = correction->mDistances[i];
-            if (i < adjustedProximityMatchedCount) {
-                multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            }
-            const float factor =
-                    SuggestUtils::getLengthScalingFactor(static_cast<float>(squaredDistance));
-            if (factor > 0.0f) {
-                multiplyRate(static_cast<int>(factor * 100.0f), &finalFreq);
-            } else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) {
-                multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
-            }
-        }
-    } else {
-        // Promotion for a word with proximity characters
-        for (int i = 0; i < adjustedProximityMatchedCount; ++i) {
-            // A word with proximity corrections
-            if (DEBUG_DICT_FULL) {
-                AKLOGI("Found a proximity correction.");
-            }
-            multiplyIntCapped(typedLetterMultiplier, &finalFreq);
-            if (i < additionalProximityCount) {
-                multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
-            } else {
-                multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
-            }
-        }
-    }
-
-    // If the user types too many(three or more) proximity characters with additional proximity
-    // character,do not treat as the same length word.
-    if (sameLength && additionalProximityCount > 0 && (adjustedProximityMatchedCount >= 3
-            || transposedCount > 0 || skipped || excessiveCount > 0)) {
-        sameLength = false;
-    }
-
-    const int errorCount = adjustedProximityMatchedCount > 0
-            ? adjustedProximityMatchedCount
-            : (proximityMatchedCount + transposedCount);
-    multiplyRate(
-            100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq);
-
-    // Promotion for an exactly matched word
-    if (ed == 0) {
-        // Full exact match
-        if (sameLength && transposedCount == 0 && !skipped && excessiveCount == 0
-                && quoteDiffCount == 0 && additionalProximityCount == 0) {
-            finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
-        }
-    }
-
-    // Promote a word with no correction
-    if (proximityMatchedCount == 0 && transposedCount == 0 && !skipped && excessiveCount == 0
-            && additionalProximityCount == 0) {
-        multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
-    }
-
-    // TODO: Check excessive count and transposed count
-    // TODO: Remove this if possible
-    /*
-         If the last character of the user input word is the same as the next character
-         of the output word, and also all of characters of the user input are matched
-         to the output word, we'll promote that word a bit because
-         that word can be considered the combination of skipped and matched characters.
-         This means that the 'sm' pattern wins over the 'ma' pattern.
-         e.g.)
-         shel -> shell [mmmma] or [mmmsm]
-         hel -> hello [mmmaa] or [mmsma]
-         m ... matching
-         s ... skipping
-         a ... traversing all
-         t ... transposing
-         e ... exceeding
-         p ... proximity matching
-     */
-    if (matchCount == inputSize && matchCount >= 2 && !skipped
-            && word[matchCount] == word[matchCount - 1]) {
-        multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq);
-    }
-
-    // TODO: Do not use sameLength?
-    if (sameLength) {
-        multiplyIntCapped(fullWordMultiplier, &finalFreq);
-    }
-
-    if (useFullEditDistance && outputLength > inputSize + 1) {
-        const int diff = outputLength - inputSize - 1;
-        const int divider = diff < 31 ? 1 << diff : S_INT_MAX;
-        finalFreq = divider > finalFreq ? 1 : finalFreq / divider;
-    }
-
-    if (DEBUG_DICT_FULL) {
-        AKLOGI("calc: %d, %d", outputLength, sameLength);
-    }
-
-    if (DEBUG_CORRECTION_FREQ
-            && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) {
-        DUMP_WORD(correction->getPrimaryInputWord(), inputSize);
-        DUMP_WORD(correction->mWord, outputLength);
-        AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount,
-                skippedCount, transposedCount, excessiveCount, additionalProximityCount,
-                outputLength, lastCharExceeded, sameLength, quoteDiffCount, ed, finalFreq);
-    }
-
-    return finalFreq;
-}
-
-/* static */ int Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(const int *freqArray,
-        const int *wordLengthArray, const int wordCount, const Correction *correction,
-        const bool isSpaceProximity, const int *word) {
-    const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
-
-    bool firstCapitalizedWordDemotion = false;
-    bool secondCapitalizedWordDemotion = false;
-
-    {
-        // TODO: Handle multiple capitalized word demotion properly
-        const int firstWordLength = wordLengthArray[0];
-        const int secondWordLength = wordLengthArray[1];
-        if (firstWordLength >= 2) {
-            firstCapitalizedWordDemotion = isUpperCase(word[0]);
-        }
-
-        if (secondWordLength >= 2) {
-            // FIXME: word[firstWordLength + 1] is incorrect.
-            secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]);
-        }
-    }
-
-
-    const bool capitalizedWordDemotion =
-            firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion;
-
-    int totalLength = 0;
-    int totalFreq = 0;
-    for (int i = 0; i < wordCount; ++i) {
-        const int wordLength = wordLengthArray[i];
-        if (wordLength <= 0) {
-            return 0;
-        }
-        totalLength += wordLength;
-        const int demotionRate = 100 - TWO_WORDS_CORRECTION_DEMOTION_BASE / (wordLength + 1);
-        int tempFirstFreq = freqArray[i];
-        multiplyRate(demotionRate, &tempFirstFreq);
-        totalFreq += tempFirstFreq;
-    }
-
-    if (totalLength <= 0 || totalFreq <= 0) {
-        return 0;
-    }
-
-    // TODO: Currently totalFreq is adjusted to two word metrix.
-    // Promote pairFreq with multiplying by 2, because the word length is the same as the typed
-    // length.
-    totalFreq = totalFreq * 2 / wordCount;
-    if (wordCount > 2) {
-        // Safety net for 3+ words -- Caveats: many heuristics and workarounds here.
-        int oneLengthCounter = 0;
-        int twoLengthCounter = 0;
-        for (int i = 0; i < wordCount; ++i) {
-            const int wordLength = wordLengthArray[i];
-            // TODO: Use bigram instead of this safety net
-            if (i < wordCount - 1) {
-                const int nextWordLength = wordLengthArray[i + 1];
-                if (wordLength == 1 && nextWordLength == 2) {
-                    // Safety net to filter 1 length and 2 length sequential words
-                    return 0;
-                }
-            }
-            const int freq = freqArray[i];
-            // Demote too short weak words
-            if (wordLength <= 4 && freq <= SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ) {
-                multiplyRate(100 * freq / MAX_PROBABILITY, &totalFreq);
-            }
-            if (wordLength == 1) {
-                ++oneLengthCounter;
-            } else if (wordLength == 2) {
-                ++twoLengthCounter;
-            }
-            if (oneLengthCounter >= 2 || (oneLengthCounter + twoLengthCounter) >= 4) {
-                // Safety net to filter too many short words
-                return 0;
-            }
-        }
-        multiplyRate(MULTIPLE_WORDS_DEMOTION_RATE, &totalFreq);
-    }
-
-    // This is a workaround to try offsetting the not-enough-demotion which will be done in
-    // calcNormalizedScore in Utils.java.
-    // In calcNormalizedScore the score will be demoted by (1 - 1 / length)
-    // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by
-    // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length))
-    const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength);
-    multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq);
-
-    // At this moment, totalFreq is calculated by the following formula:
-    // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1)))
-    //        * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1))
-
-    multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq);
-
-    // This is another workaround to offset the demotion which will be done in
-    // calcNormalizedScore in Utils.java.
-    // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote
-    // the same amount because we already have adjusted the synthetic freq of this "missing or
-    // mistyped space" suggestion candidate above in this method.
-    const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength);
-    multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq);
-
-    if (isSpaceProximity) {
-        // A word pair with one space proximity correction
-        if (DEBUG_DICT) {
-            AKLOGI("Found a word pair with space proximity correction.");
-        }
-        multiplyIntCapped(typedLetterMultiplier, &totalFreq);
-        multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq);
-    }
-
-    if (isSpaceProximity) {
-        multiplyRate(WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE, &totalFreq);
-    } else {
-        multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq);
-    }
-
-    if (capitalizedWordDemotion) {
-        multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq);
-    }
-
-    if (DEBUG_CORRECTION_FREQ) {
-        AKLOGI("Multiple words (%d, %d) (%d, %d) %d, %d", freqArray[0], freqArray[1],
-                wordLengthArray[0], wordLengthArray[1], capitalizedWordDemotion, totalFreq);
-        DUMP_WORD(word, wordLengthArray[0]);
-    }
-
-    return totalFreq;
-}
-
-/* static */ int Correction::RankingAlgorithm::editDistance(const int *before,
-        const int beforeLength, const int *after, const int afterLength) {
-    const DamerauLevenshteinEditDistancePolicy daemaruLevenshtein(
-            before, beforeLength, after, afterLength);
-    return static_cast<int>(EditDistance::getEditDistance(&daemaruLevenshtein));
-}
-
-
-// In dictionary.cpp, getSuggestion() method,
-// 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))
-//     * (individual word's score which defined in the unigram dictionary,
-//         and this score is defined in range [0, 255].)
-// Then, the following processing is applied.
-//     - If the dictionary word is matched up to the point of the user entry
-//       (full match up to min(before.length(), after.length())
-//       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
-//     - If the word is a true full match except for differences in accents or
-//       capitalization, then treat it as if the score was 255.
-//     - If before.length() == after.length()
-//       => multiply by mFullWordMultiplier (this is defined 2))
-// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2
-// For historical reasons we ignore the 1.2 modifier (because the measure for a good
-// autocorrection threshold was done at a time when it didn't exist). This doesn't change
-// the result.
-// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2.
-
-/* static */ float Correction::RankingAlgorithm::calcNormalizedScore(const int *before,
-        const int beforeLength, const int *after, const int afterLength, const int score) {
-    if (0 == beforeLength || 0 == afterLength) {
-        return 0.0f;
-    }
-    const int distance = editDistance(before, beforeLength, after, afterLength);
-    int spaceCount = 0;
-    for (int i = 0; i < afterLength; ++i) {
-        if (after[i] == KEYCODE_SPACE) {
-            ++spaceCount;
-        }
-    }
-
-    if (spaceCount == afterLength) {
-        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);
-
-    return (static_cast<float>(score) / maxScore) * weight;
-}
-} // namespace latinime
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
deleted file mode 100644
index a9e9b48..0000000
--- a/native/jni/src/correction.h
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_CORRECTION_H
-#define LATINIME_CORRECTION_H
-
-#include <cstring> // for memset()
-
-#include "correction_state.h"
-#include "defines.h"
-#include "proximity_info_state.h"
-
-namespace latinime {
-
-class ProximityInfo;
-
-class Correction {
- public:
-    typedef enum {
-        TRAVERSE_ALL_ON_TERMINAL,
-        TRAVERSE_ALL_NOT_ON_TERMINAL,
-        UNRELATED,
-        ON_TERMINAL,
-        NOT_ON_TERMINAL
-    } CorrectionType;
-
-    Correction()
-            : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false),
-              mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0),
-              mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0),
-              mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0),
-              mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0),
-              mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0),
-              mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false),
-              mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false),
-              mSkipping(false), mProximityInfoState() {
-        memset(mWord, 0, sizeof(mWord));
-        memset(mDistances, 0, sizeof(mDistances));
-        memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable));
-        // NOTE: mCorrectionStates is an array of instances.
-        // No need to initialize it explicitly here.
-    }
-
-    // Non virtual inline destructor -- never inherit this class
-    ~Correction() {}
-    void resetCorrection();
-    void initCorrection(const ProximityInfo *pi, const int inputSize, const int maxDepth);
-    void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll);
-
-    // TODO: remove
-    void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos,
-            const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance,
-            const bool doAutoCompletion, const int maxErrors);
-    void checkState() const;
-    bool sameAsTyped() const;
-    bool initProcessState(const int index);
-
-    int getInputIndex() const;
-
-    bool needsToPrune() const;
-
-    int pushAndGetTotalTraverseCount() {
-        return ++mTotalTraverseCount;
-    }
-
-    int getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
-            const int wordCount, const bool isSpaceProximity, const int *word) const;
-    int getFinalProbability(const int probability, int **word, int *wordLength);
-    int getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength,
-            const int inputSize);
-
-    CorrectionType processCharAndCalcState(const int c, const bool isTerminal);
-
-    /////////////////////////
-    // Tree helper methods
-    int goDownTree(const int parentIndex, const int childCount, const int firstChildPos);
-
-    inline int getTreeSiblingPos(const int index) const {
-        return mCorrectionStates[index].mSiblingPos;
-    }
-
-    inline void setTreeSiblingPos(const int index, const int pos) {
-        mCorrectionStates[index].mSiblingPos = pos;
-    }
-
-    inline int getTreeParentIndex(const int index) const {
-        return mCorrectionStates[index].mParentIndex;
-    }
-
-    class RankingAlgorithm {
-     public:
-        static int calculateFinalProbability(const int inputIndex, const int depth,
-                const int probability, int *editDistanceTable, const Correction *correction,
-                const int inputSize);
-        static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray,
-                const int wordCount, const Correction *correction, const bool isSpaceProximity,
-                const int *word);
-        static float calcNormalizedScore(const int *before, const int beforeLength,
-                const int *after, const int afterLength, const int score);
-        static int editDistance(const int *before, const int beforeLength, const int *after,
-                const int afterLength);
-     private:
-        static const int MAX_INITIAL_SCORE = 255;
-    };
-
-    // proximity info state
-    void initInputParams(const ProximityInfo *proximityInfo, const int *inputCodes,
-            const int inputSize, const int *xCoordinates, const int *yCoordinates) {
-        mProximityInfoState.initInputParams(0, static_cast<float>(MAX_VALUE_FOR_WEIGHTING),
-                proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false);
-    }
-
-    const int *getPrimaryInputWord() const {
-        return mProximityInfoState.getPrimaryInputWord();
-    }
-
-    int getPrimaryCodePointAt(const int index) const {
-        return mProximityInfoState.getPrimaryCodePointAt(index);
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(Correction);
-
-    /////////////////////////
-    // static inline utils //
-    /////////////////////////
-    static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
-    static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
-        return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
-    }
-
-    static const int TWO_31ST_DIV_2 = S_INT_MAX / 2;
-    AK_FORCE_INLINE static void multiplyIntCapped(const int multiplier, int *base) {
-        const int temp = *base;
-        if (temp != S_INT_MAX) {
-            // Branch if multiplier == 2 for the optimization
-            if (multiplier < 0) {
-                if (DEBUG_DICT) {
-                    ASSERT(false);
-                }
-                AKLOGI("--- Invalid multiplier: %d", multiplier);
-            } else if (multiplier == 0) {
-                *base = 0;
-            } else if (multiplier == 2) {
-                *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX;
-            } else {
-                // TODO: This overflow check gives a wrong answer when, for example,
-                //       temp = 2^16 + 1 and multiplier = 2^17 + 1.
-                //       Fix this behavior.
-                const int tempRetval = temp * multiplier;
-                *base = tempRetval >= temp ? tempRetval : S_INT_MAX;
-            }
-        }
-    }
-
-    AK_FORCE_INLINE static int powerIntCapped(const int base, const int n) {
-        if (n <= 0) return 1;
-        if (base == 2) {
-            return n < 31 ? 1 << n : S_INT_MAX;
-        }
-        int ret = base;
-        for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret);
-        return ret;
-    }
-
-    AK_FORCE_INLINE static void multiplyRate(const int rate, int *freq) {
-        if (*freq != S_INT_MAX) {
-            if (*freq > 1000000) {
-                *freq /= 100;
-                multiplyIntCapped(rate, freq);
-            } else {
-                multiplyIntCapped(rate, freq);
-                *freq /= 100;
-            }
-        }
-    }
-
-    inline int getSpaceProximityPos() const {
-        return mSpaceProximityPos;
-    }
-    inline int getMissingSpacePos() const {
-        return mMissingSpacePos;
-    }
-
-    inline int getSkipPos() const {
-        return mSkipPos;
-    }
-
-    inline int getExcessivePos() const {
-        return mExcessivePos;
-    }
-
-    inline int getTransposedPos() const {
-        return mTransposedPos;
-    }
-
-    inline void incrementInputIndex();
-    inline void incrementOutputIndex();
-    inline void startToTraverseAllNodes();
-    inline bool isSingleQuote(const int c);
-    inline CorrectionType processSkipChar(const int c, const bool isTerminal,
-            const bool inputIndexIncremented);
-    inline CorrectionType processUnrelatedCorrectionType();
-    inline void addCharToCurrentWord(const int c);
-    inline int getFinalProbabilityInternal(const int probability, int **word, int *wordLength,
-            const int inputSize);
-
-    static const int TYPED_LETTER_MULTIPLIER = 2;
-    static const int FULL_WORD_MULTIPLIER = 2;
-    const ProximityInfo *mProximityInfo;
-
-    bool mUseFullEditDistance;
-    bool mDoAutoCompletion;
-    int mMaxEditDistance;
-    int mMaxDepth;
-    int mInputSize;
-    int mSpaceProximityPos;
-    int mMissingSpacePos;
-    int mTerminalInputIndex;
-    int mTerminalOutputIndex;
-    int mMaxErrors;
-
-    int mTotalTraverseCount;
-
-    // The following arrays are state buffer.
-    int mWord[MAX_WORD_LENGTH];
-    int mDistances[MAX_WORD_LENGTH];
-
-    // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N.
-    // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot.
-    int mEditDistanceTable[(MAX_WORD_LENGTH + 1) * (MAX_WORD_LENGTH + 1)];
-
-    CorrectionState mCorrectionStates[MAX_WORD_LENGTH];
-
-    // The following member variables are being used as cache values of the correction state.
-    bool mNeedsToTraverseAllNodes;
-    int mOutputIndex;
-    int mInputIndex;
-
-    int mEquivalentCharCount;
-    int mProximityCount;
-    int mExcessiveCount;
-    int mTransposedCount;
-    int mSkippedCount;
-
-    int mTransposedPos;
-    int mExcessivePos;
-    int mSkipPos;
-
-    bool mLastCharExceeded;
-
-    bool mMatching;
-    bool mProximityMatching;
-    bool mAdditionalProximityMatching;
-    bool mExceeding;
-    bool mTransposing;
-    bool mSkipping;
-    ProximityInfoState mProximityInfoState;
-};
-
-inline void Correction::incrementInputIndex() {
-    ++mInputIndex;
-}
-
-AK_FORCE_INLINE void Correction::incrementOutputIndex() {
-    ++mOutputIndex;
-    mCorrectionStates[mOutputIndex].mParentIndex = mCorrectionStates[mOutputIndex - 1].mParentIndex;
-    mCorrectionStates[mOutputIndex].mChildCount = mCorrectionStates[mOutputIndex - 1].mChildCount;
-    mCorrectionStates[mOutputIndex].mSiblingPos = mCorrectionStates[mOutputIndex - 1].mSiblingPos;
-    mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex;
-    mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes;
-
-    mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount;
-    mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount;
-    mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount;
-    mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount;
-    mCorrectionStates[mOutputIndex].mSkippedCount = mSkippedCount;
-
-    mCorrectionStates[mOutputIndex].mSkipPos = mSkipPos;
-    mCorrectionStates[mOutputIndex].mTransposedPos = mTransposedPos;
-    mCorrectionStates[mOutputIndex].mExcessivePos = mExcessivePos;
-
-    mCorrectionStates[mOutputIndex].mLastCharExceeded = mLastCharExceeded;
-
-    mCorrectionStates[mOutputIndex].mMatching = mMatching;
-    mCorrectionStates[mOutputIndex].mProximityMatching = mProximityMatching;
-    mCorrectionStates[mOutputIndex].mAdditionalProximityMatching = mAdditionalProximityMatching;
-    mCorrectionStates[mOutputIndex].mTransposing = mTransposing;
-    mCorrectionStates[mOutputIndex].mExceeding = mExceeding;
-    mCorrectionStates[mOutputIndex].mSkipping = mSkipping;
-}
-
-inline void Correction::startToTraverseAllNodes() {
-    mNeedsToTraverseAllNodes = true;
-}
-
-AK_FORCE_INLINE bool Correction::isSingleQuote(const int c) {
-    const int userTypedChar = mProximityInfoState.getPrimaryCodePointAt(mInputIndex);
-    return (c == KEYCODE_SINGLE_QUOTE && userTypedChar != KEYCODE_SINGLE_QUOTE);
-}
-
-AK_FORCE_INLINE Correction::CorrectionType Correction::processSkipChar(const int c,
-        const bool isTerminal, const bool inputIndexIncremented) {
-    addCharToCurrentWord(c);
-    mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0);
-    mTerminalOutputIndex = mOutputIndex;
-    incrementOutputIndex();
-    if (mNeedsToTraverseAllNodes && isTerminal) {
-        return TRAVERSE_ALL_ON_TERMINAL;
-    }
-    return TRAVERSE_ALL_NOT_ON_TERMINAL;
-}
-
-inline Correction::CorrectionType Correction::processUnrelatedCorrectionType() {
-    // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType
-    mTerminalInputIndex = mInputIndex;
-    mTerminalOutputIndex = mOutputIndex;
-    return UNRELATED;
-}
-
-AK_FORCE_INLINE static void calcEditDistanceOneStep(int *editDistanceTable, const int *input,
-        const int inputSize, const int *output, const int outputLength) {
-    // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH] is not touched.
-    // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j].
-    // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated,
-    // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize].
-    int *const current = editDistanceTable + outputLength * (inputSize + 1);
-    const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1);
-    const int *const prevprev =
-            outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0;
-    current[0] = outputLength;
-    const int co = toBaseLowerCase(output[outputLength - 1]);
-    const int prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0;
-    for (int i = 1; i <= inputSize; ++i) {
-        const int ci = toBaseLowerCase(input[i - 1]);
-        const int cost = (ci == co) ? 0 : 1;
-        current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost));
-        if (i >= 2 && prevprev && ci == prevCO && co == toBaseLowerCase(input[i - 2])) {
-            current[i] = min(current[i], prevprev[i - 2] + 1);
-        }
-    }
-}
-
-AK_FORCE_INLINE void Correction::addCharToCurrentWord(const int c) {
-    mWord[mOutputIndex] = c;
-    const int *primaryInputWord = mProximityInfoState.getPrimaryInputWord();
-    calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize, mWord,
-            mOutputIndex + 1);
-}
-
-inline int Correction::getFinalProbabilityInternal(const int probability, int **word,
-        int *wordLength, const int inputSize) {
-    const int outputIndex = mTerminalOutputIndex;
-    const int inputIndex = mTerminalInputIndex;
-    *wordLength = outputIndex + 1;
-    *word = mWord;
-    int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability(
-            inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize);
-    return finalProbability;
-}
-
-} // namespace latinime
-#endif // LATINIME_CORRECTION_H
diff --git a/native/jni/src/correction_state.h b/native/jni/src/correction_state.h
deleted file mode 100644
index a63d4aa..0000000
--- a/native/jni/src/correction_state.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_CORRECTION_STATE_H
-#define LATINIME_CORRECTION_STATE_H
-
-#include <stdint.h>
-
-#include "defines.h"
-
-namespace latinime {
-
-struct CorrectionState {
-    int mParentIndex;
-    int mSiblingPos;
-    uint16_t mChildCount;
-    uint8_t mInputIndex;
-
-    uint8_t mEquivalentCharCount;
-    uint8_t mProximityCount;
-    uint8_t mTransposedCount;
-    uint8_t mExcessiveCount;
-    uint8_t mSkippedCount;
-
-    int8_t mTransposedPos;
-    int8_t mExcessivePos;
-    int8_t mSkipPos; // should be signed
-
-    // TODO: int?
-    bool mLastCharExceeded;
-
-    bool mMatching;
-    bool mTransposing;
-    bool mExceeding;
-    bool mSkipping;
-    bool mProximityMatching;
-    bool mAdditionalProximityMatching;
-
-    bool mNeedsToTraverseAllNodes;
-};
-
-inline static void initCorrectionState(CorrectionState *state, const int rootPos,
-        const uint16_t childCount, const bool traverseAll) {
-    state->mParentIndex = -1;
-    state->mChildCount = childCount;
-    state->mInputIndex = 0;
-    state->mSiblingPos = rootPos;
-    state->mNeedsToTraverseAllNodes = traverseAll;
-
-    state->mTransposedPos = -1;
-    state->mExcessivePos = -1;
-    state->mSkipPos = -1;
-
-    state->mEquivalentCharCount = 0;
-    state->mProximityCount = 0;
-    state->mTransposedCount = 0;
-    state->mExcessiveCount = 0;
-    state->mSkippedCount = 0;
-
-    state->mLastCharExceeded = false;
-
-    state->mMatching = false;
-    state->mProximityMatching = false;
-    state->mTransposing = false;
-    state->mExceeding = false;
-    state->mSkipping = false;
-    state->mAdditionalProximityMatching = false;
-}
-} // namespace latinime
-#endif // LATINIME_CORRECTION_STATE_H
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index eb59744..34a646f 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -35,6 +35,56 @@
 // Must be equal to ProximityInfo.MAX_PROXIMITY_CHARS_SIZE in Java
 #define MAX_PROXIMITY_CHARS_SIZE 16
 #define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
+
+AK_FORCE_INLINE static int intArrayToCharArray(const int *const source, const int sourceSize,
+        char *dest, const int destSize) {
+    // We want to always terminate with a 0 char, so stop one short of the length to make
+    // sure there is room.
+    const int destLimit = destSize - 1;
+    int si = 0;
+    int di = 0;
+    while (si < sourceSize && di < destLimit && 0 != source[si]) {
+        const int codePoint = source[si++];
+        if (codePoint < 0x7F) { // One byte
+            dest[di++] = codePoint;
+        } else if (codePoint < 0x7FF) { // Two bytes
+            if (di + 1 >= destLimit) break;
+            dest[di++] = 0xC0 + (codePoint >> 6);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint < 0xFFFF) { // Three bytes
+            if (di + 2 >= destLimit) break;
+            dest[di++] = 0xE0 + (codePoint >> 12);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x1FFFFF) { // Four bytes
+            if (di + 3 >= destLimit) break;
+            dest[di++] = 0xF0 + (codePoint >> 18);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x3FFFFFF) { // Five bytes
+            if (di + 4 >= destLimit) break;
+            dest[di++] = 0xF8 + (codePoint >> 24);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else if (codePoint <= 0x7FFFFFFF) { // Six bytes
+            if (di + 5 >= destLimit) break;
+            dest[di++] = 0xFC + (codePoint >> 30);
+            dest[di++] = 0x80 + ((codePoint >> 24) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else {
+            // Not a code point... skip.
+        }
+    }
+    dest[di] = 0;
+    return di;
+}
 
 #if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
 #include <android/log.h>
@@ -46,35 +96,13 @@
 
 #define DUMP_RESULT(words, frequencies) do { dumpResult(words, frequencies); } while (0)
 #define DUMP_WORD(word, length) do { dumpWord(word, length); } while (0)
-#define INTS_TO_CHARS(input, length, output) do { \
-        intArrayToCharArray(input, length, output); } while (0)
-
-// TODO: Support full UTF-8 conversion
-AK_FORCE_INLINE static int intArrayToCharArray(const int *source, const int sourceSize,
-        char *dest) {
-    int si = 0;
-    int di = 0;
-    while (si < sourceSize && di < MAX_WORD_LENGTH - 1 && 0 != source[si]) {
-        const int codePoint = source[si++];
-        if (codePoint < 0x7F) {
-            dest[di++] = codePoint;
-        } else if (codePoint < 0x7FF) {
-            dest[di++] = 0xC0 + (codePoint >> 6);
-            dest[di++] = 0x80 + (codePoint & 0x3F);
-        } else if (codePoint < 0xFFFF) {
-            dest[di++] = 0xE0 + (codePoint >> 12);
-            dest[di++] = 0x80 + ((codePoint & 0xFC0) >> 6);
-            dest[di++] = 0x80 + (codePoint & 0x3F);
-        }
-    }
-    dest[di] = 0;
-    return di;
-}
+#define INTS_TO_CHARS(input, length, output, outlength) do { \
+        intArrayToCharArray(input, length, output, outlength); } while (0)
 
 static inline void dumpWordInfo(const int *word, const int length, const int rank,
         const int probability) {
     static char charBuf[50];
-    const int N = intArrayToCharArray(word, length, charBuf);
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
     if (N > 1) {
         AKLOGI("%2d [ %s ] (%d)", rank, charBuf, probability);
     }
@@ -90,7 +118,7 @@
 
 static AK_FORCE_INLINE void dumpWord(const int *word, const int length) {
     static char charBuf[50];
-    const int N = intArrayToCharArray(word, length, charBuf);
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
     if (N > 1) {
         AKLOGI("[ %s ]", charBuf);
     }
@@ -203,14 +231,12 @@
 #define DEBUG_DICT true
 #define DEBUG_DICT_FULL false
 #define DEBUG_EDIT_DISTANCE false
-#define DEBUG_SHOW_FOUND_WORD false
 #define DEBUG_NODE DEBUG_DICT_FULL
 #define DEBUG_TRACE DEBUG_DICT_FULL
 #define DEBUG_PROXIMITY_INFO false
 #define DEBUG_PROXIMITY_CHARS false
 #define DEBUG_CORRECTION false
 #define DEBUG_CORRECTION_FREQ false
-#define DEBUG_WORDS_PRIORITY_QUEUE false
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
@@ -229,14 +255,12 @@
 #define DEBUG_DICT false
 #define DEBUG_DICT_FULL false
 #define DEBUG_EDIT_DISTANCE false
-#define DEBUG_SHOW_FOUND_WORD false
 #define DEBUG_NODE false
 #define DEBUG_TRACE false
 #define DEBUG_PROXIMITY_INFO false
 #define DEBUG_PROXIMITY_CHARS false
 #define DEBUG_CORRECTION false
 #define DEBUG_CORRECTION_FREQ false
-#define DEBUG_WORDS_PRIORITY_QUEUE false
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
@@ -268,82 +292,25 @@
 // of the binary dictionary where a {key,value} string pair scheme is used.
 #define LARGEST_INT_DIGIT_COUNT 11
 
-// Define this to use mmap() for dictionary loading.  Undefine to use malloc() instead of mmap().
-// We measured and compared performance of both, and found mmap() is fairly good in terms of
-// loading time, and acceptable even for several initial lookups which involve page faults.
-#define USE_MMAP_FOR_DICTIONARY
-
-#define NOT_VALID_WORD (-99)
+#define NOT_A_VALID_WORD_POS (-99)
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
-#define MATCH_CHAR_WITHOUT_DISTANCE_INFO (-2)
-#define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO (-3)
-#define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO (-4)
 #define NOT_AN_INDEX (-1)
 #define NOT_A_PROBABILITY (-1)
+#define NOT_A_DICT_POS (S_INT_MIN)
 
 #define KEYCODE_SPACE ' '
 #define KEYCODE_SINGLE_QUOTE '\''
 #define KEYCODE_HYPHEN_MINUS '-'
 
-#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.
-#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 80
-#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X 12
-#define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 58
-#define WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE 50
-#define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
-#define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
-#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 70
-#define FULL_MATCHED_WORDS_PROMOTION_RATE 120
-#define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
-#define WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE 70
-#define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
-#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE 148
-#define WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER 3
-#define CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE 45
-#define INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE 70
-#define FIRST_CHAR_DIFFERENT_DEMOTION_RATE 96
-#define TWO_WORDS_CAPITALIZED_DEMOTION_RATE 50
-#define TWO_WORDS_CORRECTION_DEMOTION_BASE 80
-#define TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER 1
-#define ZERO_DISTANCE_PROMOTION_RATE 110.0f
-#define NEUTRAL_SCORE_SQUARED_RADIUS 8.0f
-#define HALF_SCORE_SQUARED_RADIUS 32.0f
 #define MAX_PROBABILITY 255
 #define MAX_BIGRAM_ENCODED_PROBABILITY 15
 
 // Assuming locale strings such as en_US, sr-Latn etc.
 #define MAX_LOCALE_STRING_LENGTH 10
 
-// Word limit for sub queues used in WordsPriorityQueuePool.  Sub queues are temporary queues used
-// for better performance.
-// Holds up to 1 candidate for each word
-#define SUB_QUEUE_MAX_WORDS 1
-#define SUB_QUEUE_MAX_COUNT 10
-#define SUB_QUEUE_MIN_WORD_LENGTH 4
-// TODO: Extend this limitation
-#define MULTIPLE_WORDS_SUGGESTION_MAX_WORDS 5
-// TODO: Remove this limitation
-#define MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH 12
-// TODO: Remove this limitation
-#define MULTIPLE_WORDS_SUGGESTION_MAX_TOTAL_TRAVERSE_COUNT 45
-#define MULTIPLE_WORDS_DEMOTION_RATE 80
-#define MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION 6
-
-#define TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.35f
-#define START_TWO_WORDS_CORRECTION_THRESHOLD 0.185f
-/* heuristic... This should be changed if we change the unit of the probability. */
-#define SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ (MAX_PROBABILITY * 58 / 100)
-
-#define MAX_DEPTH_MULTIPLIER 3
-#define FIRST_WORD_INDEX 0
-
 // Max value for length, distance and probability which are used in weighting
 // TODO: Remove
 #define MAX_VALUE_FOR_WEIGHTING 10000000
@@ -351,48 +318,20 @@
 // The max number of the keys in one keyboard layout
 #define MAX_KEY_COUNT_IN_A_KEYBOARD 64
 
-// TODO: Reduce this constant if possible; check the maximum number of digraphs in the same
-// word in the dictionary for languages with digraphs, like German and French
-#define DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH 5
-
-#define MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION 3
-
 // TODO: Remove
 #define MAX_POINTER_COUNT 1
 #define MAX_POINTER_COUNT_G 2
 
-// Size, in bytes, of the bloom filter index for bigrams
-// 128 gives us 1024 buckets. The probability of false positive is (1 - e ** (-kn/m))**k,
-// where k is the number of hash functions, n the number of bigrams, and m the number of
-// bits we can test.
-// At the moment 100 is the maximum number of bigrams for a word with the current
-// dictionaries, so n = 100. 1024 buckets give us m = 1024.
-// With 1 hash function, our false positive rate is about 9.3%, which should be enough for
-// our uses since we are only using this to increase average performance. For the record,
-// k = 2 gives 3.1% and k = 3 gives 1.6%. With k = 1, making m = 2048 gives 4.8%,
-// and m = 4096 gives 2.4%.
-#define BIGRAM_FILTER_BYTE_SIZE 128
-// Must be smaller than BIGRAM_FILTER_BYTE_SIZE * 8, and preferably prime. 1021 is the largest
-// prime under 128 * 8.
-#define BIGRAM_FILTER_MODULO 1021
-#if BIGRAM_FILTER_BYTE_SIZE * 8 < BIGRAM_FILTER_MODULO
-#error "BIGRAM_FILTER_MODULO is larger than BIGRAM_FILTER_BYTE_SIZE"
-#endif
-
-// Max number of bigram maps (previous word contexts) to be cached. Increasing this number could
-// improve bigram lookup speed for multi-word suggestions, but at the cost of more memory usage.
-// Also, there are diminishing returns since the most frequently used bigrams are typically near
-// the beginning of the input and are thus the first ones to be cached. Note that these bigrams
-// are reset for each new composing word.
-#define MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP 25
-// Most common previous word contexts currently have 100 bigrams
-#define DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP 100
+// Queue IDs and size for DicNodesCache
+#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE 0
+#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE 1
+#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL 2
+#define DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION 3
+#define DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE 4
 
 template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
 template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
 
-#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
-
 // DEBUG
 #define INPUTLENGTH_FOR_DEBUG (-1)
 #define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
@@ -442,6 +381,7 @@
     CT_TRANSPOSITION,
     CT_COMPLETION,
     CT_TERMINAL,
+    CT_TERMINAL_INSERTION,
     // Create new word with space omission
     CT_NEW_WORD_SPACE_OMITTION,
     // Create new word with space substitution
diff --git a/native/jni/src/dic_traverse_wrapper.cpp b/native/jni/src/dic_traverse_wrapper.cpp
deleted file mode 100644
index 88ca9fa..0000000
--- a/native/jni/src/dic_traverse_wrapper.cpp
+++ /dev/null
@@ -1,26 +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.
- */
-
-#define LOG_TAG "LatinIME: jni: Session"
-
-#include "dic_traverse_wrapper.h"
-
-namespace latinime {
-void *(*DicTraverseWrapper::sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring) = 0;
-void (*DicTraverseWrapper::sDicTraverseSessionReleaseMethod)(void *) = 0;
-void (*DicTraverseWrapper::sDicTraverseSessionInitMethod)(
-        void *, const Dictionary *const, const int *, const int) = 0;
-} // namespace latinime
diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h
deleted file mode 100644
index 1108a45..0000000
--- a/native/jni/src/dic_traverse_wrapper.h
+++ /dev/null
@@ -1,64 +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_DIC_TRAVERSE_WRAPPER_H
-#define LATINIME_DIC_TRAVERSE_WRAPPER_H
-
-#include "defines.h"
-#include "jni.h"
-
-namespace latinime {
-class Dictionary;
-// TODO: Remove
-class DicTraverseWrapper {
- public:
-    static void *getDicTraverseSession(JNIEnv *env, jstring locale) {
-        if (sDicTraverseSessionFactoryMethod) {
-            return sDicTraverseSessionFactoryMethod(env, locale);
-        }
-        return 0;
-    }
-    static void initDicTraverseSession(void *traverseSession, const Dictionary *const dictionary,
-            const int *prevWord, const int prevWordLength) {
-        if (sDicTraverseSessionInitMethod) {
-            sDicTraverseSessionInitMethod(traverseSession, dictionary, prevWord, prevWordLength);
-        }
-    }
-    static void releaseDicTraverseSession(void *traverseSession) {
-        if (sDicTraverseSessionReleaseMethod) {
-            sDicTraverseSessionReleaseMethod(traverseSession);
-        }
-    }
-    static void setTraverseSessionFactoryMethod(void *(*factoryMethod)(JNIEnv *, jstring)) {
-        sDicTraverseSessionFactoryMethod = factoryMethod;
-    }
-    static void setTraverseSessionInitMethod(
-            void (*initMethod)(void *, const Dictionary *const, const int *, const int)) {
-        sDicTraverseSessionInitMethod = initMethod;
-    }
-    static void setTraverseSessionReleaseMethod(void (*releaseMethod)(void *)) {
-        sDicTraverseSessionReleaseMethod = releaseMethod;
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(DicTraverseWrapper);
-    static void *(*sDicTraverseSessionFactoryMethod)(JNIEnv *, jstring);
-    static void (*sDicTraverseSessionInitMethod)(
-            void *, const Dictionary *const, const int *, const int);
-    static void (*sDicTraverseSessionReleaseMethod)(void *);
-};
-} // namespace latinime
-#endif // LATINIME_DIC_TRAVERSE_WRAPPER_H
diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp
deleted file mode 100644
index dadb2ba..0000000
--- a/native/jni/src/dictionary.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2009, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "LatinIME: dictionary.cpp"
-
-#include "dictionary.h"
-
-#include <map> // TODO: remove
-#include <stdint.h>
-
-#include "bigram_dictionary.h"
-#include "binary_format.h"
-#include "defines.h"
-#include "dic_traverse_wrapper.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 {
-
-Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust)
-        : mDict(static_cast<unsigned char *>(dict)),
-          mOffsetDict((static_cast<unsigned char *>(dict))
-                  + BinaryFormat::getHeaderSize(mDict, dictSize)),
-          mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust),
-          mUnigramDictionary(new UnigramDictionary(mOffsetDict,
-                  BinaryFormat::getFlags(mDict, dictSize))),
-          mBigramDictionary(new BigramDictionary(mOffsetDict)),
-          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,
-        int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
-        int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint, bool isGesture,
-        bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices,
-        int *outputTypes) const {
-    int result = 0;
-    if (isGesture) {
-        DicTraverseWrapper::initDicTraverseSession(
-                traverseSession, this, prevWordCodePoints, prevWordLength);
-        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
-                ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
-                frequencies, spaceIndices, outputTypes);
-        if (DEBUG_DICT) {
-            DUMP_RESULT(outWords, frequencies);
-        }
-        return result;
-    } else {
-        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;
-        }
-    }
-}
-
-int Dictionary::getBigrams(const int *word, int length, int *inputCodePoints, int inputSize,
-        int *outWords, int *frequencies, int *outputTypes) const {
-    if (length <= 0) return 0;
-    return mBigramDictionary->getBigrams(word, length, inputCodePoints, inputSize, outWords,
-            frequencies, outputTypes);
-}
-
-int Dictionary::getProbability(const int *word, int length) const {
-    return mUnigramDictionary->getProbability(word, length);
-}
-
-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
deleted file mode 100644
index 2ad5b6c..0000000
--- a/native/jni/src/dictionary.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_DICTIONARY_H
-#define LATINIME_DICTIONARY_H
-
-#include <stdint.h>
-
-#include "defines.h"
-
-namespace latinime {
-
-class BigramDictionary;
-class ProximityInfo;
-class SuggestInterface;
-class UnigramDictionary;
-
-class Dictionary {
- public:
-    // Taken from SuggestedWords.java
-    static const int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
-    static const int KIND_TYPED = 0; // What user typed
-    static const int KIND_CORRECTION = 1; // Simple correction/suggestion
-    static const int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
-    static const int KIND_WHITELIST = 3; // Whitelisted word
-    static const int KIND_BLACKLIST = 4; // Blacklisted word
-    static const int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation
-    static const int KIND_APP_DEFINED = 6; // Suggested by the application
-    static const int KIND_SHORTCUT = 7; // A shortcut
-    static const int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
-
-    static const int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
-    static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
-    static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
-
-    Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust);
-
-    int getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, int *xcoordinates,
-            int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints, int inputSize,
-            int *prevWordCodePoints, int prevWordLength, int commitPoint, bool isGesture,
-            bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices,
-            int *outputTypes) const;
-
-    int getBigrams(const int *word, int length, int *inputCodePoints, int inputSize, int *outWords,
-            int *frequencies, int *outputTypes) const;
-
-    int getProbability(const int *word, int length) const;
-    bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const;
-    const uint8_t *getDict() const { // required to release dictionary buffer
-        return mDict;
-    }
-    const uint8_t *getOffsetDict() const {
-        return mOffsetDict;
-    }
-    int getDictSize() const { return mDictSize; }
-    int getMmapFd() const { return mMmapFd; }
-    int getDictBufAdjust() const { return mDictBufAdjust; }
-    int getDictFlags() const;
-    virtual ~Dictionary();
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
-    const uint8_t *mDict;
-    const uint8_t *mOffsetDict;
-
-    // Used only for the mmap version of dictionary loading, but we use these as dummy variables
-    // also for the malloc version.
-    const int mDictSize;
-    const int mMmapFd;
-    const int mDictBufAdjust;
-
-    const UnigramDictionary *mUnigramDictionary;
-    const BigramDictionary *mBigramDictionary;
-    SuggestInterface *mGestureSuggest;
-    SuggestInterface *mTypingSuggest;
-};
-} // namespace latinime
-#endif // LATINIME_DICTIONARY_H
diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h
deleted file mode 100644
index 4cbb127..0000000
--- a/native/jni/src/geometry_utils.h
+++ /dev/null
@@ -1,53 +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_GEOMETRY_UTILS_H
-#define LATINIME_GEOMETRY_UTILS_H
-
-#include <cmath>
-
-#include "defines.h"
-
-#define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
-        ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
-
-namespace latinime {
-
-static inline float SQUARE_FLOAT(const float x) { return x * x; }
-
-static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
-    const int dx = x1 - x2;
-    const int dy = y1 - y2;
-    if (dx == 0 && dy == 0) return 0.0f;
-    return atan2f(static_cast<float>(dy), static_cast<float>(dx));
-}
-
-static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) {
-    const float deltaA = fabsf(a1 - a2);
-    const float diff = ROUND_FLOAT_10000(deltaA);
-    if (diff > M_PI_F) {
-        const float normalizedDiff = 2.0f * M_PI_F - diff;
-        return ROUND_FLOAT_10000(normalizedDiff);
-    }
-    return diff;
-}
-
-static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
-        const int y2) {
-    return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
-}
-} // namespace latinime
-#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/multi_bigram_map.h b/native/jni/src/multi_bigram_map.h
deleted file mode 100644
index 7e1b630..0000000
--- a/native/jni/src/multi_bigram_map.h
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_MULTI_BIGRAM_MAP_H
-#define LATINIME_MULTI_BIGRAM_MAP_H
-
-#include <cstring>
-#include <stdint.h>
-
-#include "defines.h"
-#include "binary_format.h"
-#include "hash_map_compat.h"
-
-namespace latinime {
-
-// Class for caching bigram maps for multiple previous word contexts. This is useful since the
-// algorithm needs to look up the set of bigrams for every word pair that occurs in every
-// multi-word suggestion.
-class MultiBigramMap {
- public:
-    MultiBigramMap() : mBigramMaps() {}
-    ~MultiBigramMap() {}
-
-    // Look up the bigram probability for the given word pair from the cached bigram maps.
-    // Also caches the bigrams if there is space remaining and they have not been cached already.
-    int getBigramProbability(const uint8_t *const dicRoot, const int wordPosition,
-            const int nextWordPosition, const int unigramProbability) {
-        hash_map_compat<int, BigramMap>::const_iterator mapPosition =
-                mBigramMaps.find(wordPosition);
-        if (mapPosition != mBigramMaps.end()) {
-            return mapPosition->second.getBigramProbability(nextWordPosition, unigramProbability);
-        }
-        if (mBigramMaps.size() < MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP) {
-            addBigramsForWordPosition(dicRoot, wordPosition);
-            return mBigramMaps[wordPosition].getBigramProbability(
-                    nextWordPosition, unigramProbability);
-        }
-        return BinaryFormat::getBigramProbability(
-                dicRoot, wordPosition, nextWordPosition, unigramProbability);
-    }
-
-    void clear() {
-        mBigramMaps.clear();
-    }
-
- private:
-    DISALLOW_COPY_AND_ASSIGN(MultiBigramMap);
-
-    class BigramMap {
-     public:
-        BigramMap() : mBigramMap(DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP) {}
-        ~BigramMap() {}
-
-        void init(const uint8_t *const dicRoot, int position) {
-            BinaryFormat::fillBigramProbabilityToHashMap(dicRoot, position, &mBigramMap);
-        }
-
-        inline int getBigramProbability(const int nextWordPosition, const int unigramProbability)
-                const {
-           return BinaryFormat::getBigramProbabilityFromHashMap(
-                   nextWordPosition, &mBigramMap, unigramProbability);
-        }
-
-     private:
-        // Note: Default copy constructor needed for use in hash_map.
-        hash_map_compat<int, int> mBigramMap;
-    };
-
-    void addBigramsForWordPosition(const uint8_t *const dicRoot, const int position) {
-        mBigramMaps[position].init(dicRoot, position);
-    }
-
-    hash_map_compat<int, BigramMap> mBigramMaps;
-};
-} // namespace latinime
-#endif // LATINIME_MULTI_BIGRAM_MAP_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.cpp b/native/jni/src/suggest/core/dicnode/dic_node.cpp
index 8c48c58..de088c7 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "dic_node.h"
+#include "suggest/core/dicnode/dic_node.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 4225bb3..cdd9f59 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -17,26 +17,27 @@
 #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"
+#include "suggest/core/dicnode/dic_node_profiler.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
+#include "suggest/core/dicnode/internal/dic_node_state.h"
+#include "suggest/core/dicnode/internal/dic_node_properties.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+#include "utils/char_utils.h"
 
 #if DEBUG_DICT
 #define LOGI_SHOW_ADD_COST_PROP \
         do { char charBuf[50]; \
-        INTS_TO_CHARS(getOutputWordBuf(), getDepth(), charBuf); \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
         AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \
                 __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \
                 getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0)
 #define DUMP_WORD_AND_SCORE(header) \
         do { char charBuf[50]; char prevWordCharBuf[50]; \
-        INTS_TO_CHARS(getOutputWordBuf(), getDepth(), charBuf); \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
         INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
-                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf); \
+                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \
+                NELEMS(prevWordCharBuf)); \
         AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d,,", header, \
                 getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
                 getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
@@ -51,6 +52,11 @@
 
 // This struct is purely a bucket to return values. No instances of this struct should be kept.
 struct DicNode_InputStateG {
+    DicNode_InputStateG()
+            : mNeedsToUpdateInputStateG(false), mPointerId(0), mInputIndex(0),
+              mPrevCodePoint(0), mTerminalDiffCost(0.0f), mRawLength(0.0f),
+              mDoubleLetterLevel(NOT_A_DOUBLE_LETTER) {}
+
     bool mNeedsToUpdateInputStateG;
     int mPointerId;
     int16_t mInputIndex;
@@ -92,7 +98,6 @@
     DicNode &operator=(const DicNode &dicNode);
     virtual ~DicNode() {}
 
-    // TODO: minimize arguments by looking binary_format
     // Init for copy
     void initByCopy(const DicNode *dicNode) {
         mIsUsed = true;
@@ -102,35 +107,28 @@
         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) {
+    void initAsRoot(const int rootGroupPos, const int prevWordNodePos) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                pos, 0, childrenPos, 0, 0, 0, childrenCount, 0, 0, false, false, true, 0, 0);
+                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
+                true /* hasChildren */, false /* isBlacklistedOrNotAWord */, 0 /* depth */,
+                0 /* terminalDepth */);
         mDicNodeState.init(prevWordNodePos);
         PROF_NODE_RESET(mProfiler);
     }
 
-    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) {
+    void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
         mIsUsed = true;
-        mIsCachedForNextSuggestion = false;
+        mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                pos, 0, childrenPos, 0, 0, 0, childrenCount, 0, 0, false, false, true, 0, 0);
+                NOT_A_VALID_WORD_POS /* pos */, rootGroupPos, NOT_A_CODE_POINT /* nodeCodePoint */,
+                NOT_A_PROBABILITY /* probability */, false /* isTerminal */,
+                true /* hasChildren */, false /* isBlacklistedOrNotAWord */,  0 /* depth */,
+                0 /* terminalDepth */);
         // TODO: Move to dicNodeState?
         mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
         mDicNodeState.mDicNodeStateInput.init(
@@ -150,21 +148,28 @@
         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) {
+    void initAsPassingChild(DicNode *parentNode) {
         mIsUsed = true;
-        uint16_t newDepth = static_cast<uint16_t>(dicNode->getDepth() + 1);
+        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
+        const int c = parentNode->getNodeTypedCodePoint();
+        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
+        mDicNodeState.init(&parentNode->mDicNodeState);
+        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
+    }
+
+    void initAsChild(const DicNode *const dicNode, const int pos, const int childrenPos,
+            const int probability, const bool isTerminal, const bool hasChildren,
+            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        mIsUsed = true;
+        uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
-                dicNode->mDicNodeProperties.getLeavingDepth() + additionalSubwordLength);
-        mDicNodeProperties.init(pos, flags, childrenPos, attributesPos, siblingPos, nodeCodePoint,
-                childrenCount, probability, bigramProbability, isTerminal, hasMultipleChars,
-                hasChildren, newDepth, newLeavingDepth);
-        mDicNodeState.init(&dicNode->mDicNodeState, additionalSubwordLength, additionalSubword);
+                dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
+        mDicNodeProperties.init(pos, childrenPos, mergedNodeCodePoints[0], probability,
+                isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth, newLeavingDepth);
+        mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
@@ -180,7 +185,7 @@
     }
 
     bool isRoot() const {
-        return getDepth() == 0;
+        return getNodeCodePointCount() == 0;
     }
 
     bool hasChildren() const {
@@ -188,12 +193,12 @@
     }
 
     bool isLeavingNode() const {
-        ASSERT(getDepth() <= getLeavingDepth());
-        return getDepth() == getLeavingDepth();
+        ASSERT(getNodeCodePointCount() <= mDicNodeProperties.getLeavingDepth());
+        return getNodeCodePointCount() == mDicNodeProperties.getLeavingDepth();
     }
 
     AK_FORCE_INLINE bool isFirstLetter() const {
-        return getDepth() == 1;
+        return getNodeCodePointCount() == 1;
     }
 
     bool isCached() const {
@@ -206,26 +211,30 @@
 
     // Used to expand the node in DicNodeUtils
     int getNodeTypedCodePoint() const {
-        return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getDepth());
+        return mDicNodeState.mDicNodeStateOutput.getCodePointAt(getNodeCodePointCount());
     }
 
-    bool isImpossibleBigramWord() const {
-        if (mDicNodeProperties.hasBlacklistedOrNotAWordFlag()) {
-            return true;
+    // Check if the current word and the previous word can be considered as a valid multiple word
+    // suggestion.
+    bool isValidMultipleWordSuggestion() const {
+        if (isBlacklistedOrNotAWord()) {
+            return false;
         }
+        // Treat suggestion as invalid if the current and the previous word are single character
+        // words.
         const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
                 - mDicNodeState.mDicNodeStatePrevWord.getPrevWordStart() - 1;
-        const int currentWordLen = getDepth();
-        return (prevWordLen == 1 && currentWordLen == 1);
+        const int currentWordLen = getNodeCodePointCount();
+        return (prevWordLen != 1 || currentWordLen != 1);
     }
 
     bool isFirstCharUppercase() const {
         const int c = getOutputWordBuf()[0];
-        return isAsciiUpper(c);
+        return CharUtils::isAsciiUpper(c);
     }
 
     bool isFirstWord() const {
-        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_VALID_WORD;
+        return mDicNodeState.mDicNodeStatePrevWord.getPrevWordNodePos() == NOT_A_VALID_WORD_POS;
     }
 
     bool isCompletion(const int inputSize) const {
@@ -251,37 +260,27 @@
         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 currentNodeDepth = getNodeCodePointCount();
         const int terminalNodeDepth = mDicNodeProperties.getLeavingDepth();
         return isTerminalNodes && currentNodeDepth > 0 && currentNodeDepth == terminalNodeDepth;
     }
 
     bool shouldBeFilterdBySafetyNetForBigram() const {
-        const uint16_t currentDepth = getDepth();
+        const uint16_t currentDepth = getNodeCodePointCount();
         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();
+        const int currentWordDepth = getNodeCodePointCount();
         // TODO: 3 can be 2? Needs to be investigated.
         // TODO: Have a const variable for 3 (or 2)
         return prevWordsLen + currentWordDepth > MAX_WORD_LENGTH - 3;
@@ -316,7 +315,7 @@
 
     void outputResult(int *dest) const {
         const uint16_t prevWordLength = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
-        const uint16_t currentDepth = getDepth();
+        const uint16_t currentDepth = getNodeCodePointCount();
         DicNodeUtils::appendTwoWords(mDicNodeState.mDicNodeStatePrevWord.mPrevWord,
                    prevWordLength, getOutputWordBuf(), currentDepth, dest);
         DUMP_WORD_AND_SCORE("OUTPUT");
@@ -330,12 +329,12 @@
         return mDicNodeState.mDicNodeStatePrevWord.getPrevWordCount() > 0;
     }
 
-    float getProximityCorrectionCount() const {
-        return static_cast<float>(mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount());
+    int getProximityCorrectionCount() const {
+        return mDicNodeState.mDicNodeStateScoring.getProximityCorrectionCount();
     }
 
-    float getEditCorrectionCount() const {
-        return static_cast<float>(mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount());
+    int getEditCorrectionCount() const {
+        return mDicNodeState.mDicNodeStateScoring.getEditCorrectionCount();
     }
 
     // Used to prune nodes
@@ -365,7 +364,7 @@
     }
 
     AK_FORCE_INLINE const int *getOutputWordBuf() const {
-        return mDicNodeState.mDicNodeStateOutput.mWordBuf;
+        return mDicNodeState.mDicNodeStateOutput.mCodePointsBuf;
     }
 
     int getPrevCodePointG(int pointerId) const {
@@ -375,7 +374,7 @@
     // 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());
+        return CharUtils::isIntentionalOmissionCodePoint(getNodeCodePoint());
     }
 
     // Whether the omission is so frequent that it should incur zero cost.
@@ -467,18 +466,19 @@
         return mDicNodeState.mDicNodeStateScoring.isExactMatch();
     }
 
-    uint8_t getFlags() const {
-        return mDicNodeProperties.getFlags();
+    bool isBlacklistedOrNotAWord() const {
+        return mDicNodeProperties.isBlacklistedOrNotAWord();
     }
 
-    int getAttributesPos() const {
-        return mDicNodeProperties.getAttributesPos();
-    }
-
-    inline uint16_t getDepth() const {
+    inline uint16_t getNodeCodePointCount() const {
         return mDicNodeProperties.getDepth();
     }
 
+    // Returns code point count including spaces
+    inline uint16_t getTotalNodeCodePointCount() const {
+        return getNodeCodePointCount() + mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength();
+    }
+
     AK_FORCE_INLINE void dump(const char *tag) const {
 #if DEBUG_DICT
         DUMP_WORD_AND_SCORE(tag);
@@ -503,6 +503,12 @@
         if (!right->isUsed()) {
             return false;
         }
+        // Promote exact matches to prevent them from being pruned.
+        const bool leftExactMatch = isExactMatch();
+        const bool rightExactMatch = right->isExactMatch();
+        if (leftExactMatch != rightExactMatch) {
+            return leftExactMatch;
+        }
         const float diff =
                 right->getNormalizedCompoundDistance() - getNormalizedCompoundDistance();
         static const float MIN_DIFF = 0.000001f;
@@ -511,8 +517,8 @@
         } else if (diff < -MIN_DIFF) {
             return false;
         }
-        const int depth = getDepth();
-        const int depthDiff = right->getDepth() - depth;
+        const int depth = getNodeCodePointCount();
+        const int depthDiff = right->getNodeCodePointCount() - depth;
         if (depthDiff != 0) {
             return depthDiff > 0;
         }
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
index d3f28a8..2a486b8 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -21,10 +21,11 @@
 #include <vector>
 
 #include "defines.h"
-#include "dic_node.h"
-#include "dic_node_release_listener.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_release_listener.h"
 
-#define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 200
+// The biggest value among MAX_CACHE_DIC_NODE_SIZE, MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT, ...
+#define MAX_DIC_NODE_PRIORITY_QUEUE_CAPACITY 310
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_profiler.h b/native/jni/src/suggest/core/dicnode/dic_node_profiler.h
index 90f75d0..1f4d257 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_profiler.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_profiler.h
@@ -31,6 +31,7 @@
 #define PROF_TRANSPOSITION(profiler) profiler.profTransposition()
 #define PROF_NEARESTKEY(profiler) profiler.profNearestKey()
 #define PROF_TERMINAL(profiler) profiler.profTerminal()
+#define PROF_TERMINAL_INSERTION(profiler) profiler.profTerminalInsertion()
 #define PROF_NEW_WORD(profiler) profiler.profNewWord()
 #define PROF_NEW_WORD_BIGRAM(profiler) profiler.profNewWordBigram()
 #define PROF_NODE_RESET(profiler) profiler.reset()
@@ -47,6 +48,7 @@
 #define PROF_TRANSPOSITION(profiler)
 #define PROF_NEARESTKEY(profiler)
 #define PROF_TERMINAL(profiler)
+#define PROF_TERMINAL_INSERTION(profiler)
 #define PROF_NEW_WORD(profiler)
 #define PROF_NEW_WORD_BIGRAM(profiler)
 #define PROF_NODE_RESET(profiler)
@@ -62,7 +64,7 @@
             : mProfOmission(0), mProfInsertion(0), mProfTransposition(0),
               mProfAdditionalProximity(0), mProfSubstitution(0),
               mProfSpaceSubstitution(0), mProfSpaceOmission(0),
-              mProfMatch(0), mProfCompletion(0), mProfTerminal(0),
+              mProfMatch(0), mProfCompletion(0), mProfTerminal(0), mProfTerminalInsertion(0),
               mProfNearestKey(0), mProfNewWord(0), mProfNewWordBigram(0) {}
 
     int mProfOmission;
@@ -75,6 +77,7 @@
     int mProfMatch;
     int mProfCompletion;
     int mProfTerminal;
+    int mProfTerminalInsertion;
     int mProfNearestKey;
     int mProfNewWord;
     int mProfNewWordBigram;
@@ -123,6 +126,10 @@
         ++mProfTerminal;
     }
 
+    void profTerminalInsertion() {
+        ++mProfTerminalInsertion;
+    }
+
     void profNewWord() {
         ++mProfNewWord;
     }
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
deleted file mode 100644
index 63a6b13..0000000
--- a/native/jni/src/suggest/core/dicnode/dic_node_properties.h
+++ /dev/null
@@ -1,178 +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_DIC_NODE_PROPERTIES_H
-#define LATINIME_DIC_NODE_PROPERTIES_H
-
-#include <stdint.h>
-
-#include "binary_format.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;
-    }
-
-    bool hasBlacklistedOrNotAWordFlag() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mFlags);
-    }
-
- 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/core/dicnode/dic_node_proximity_filter.h b/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
new file mode 100644
index 0000000..1a39f2e
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
@@ -0,0 +1,58 @@
+/*
+ * 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_DIC_NODE_PROXIMITY_FILTER_H
+#define LATINIME_DIC_NODE_PROXIMITY_FILTER_H
+
+#include "defines.h"
+#include "suggest/core/layout/proximity_info_state.h"
+#include "suggest/core/layout/proximity_info_utils.h"
+#include "suggest/core/policy/dictionary_structure_policy.h"
+
+namespace latinime {
+
+class DicNodeProximityFilter : public DictionaryStructurePolicy::NodeFilter {
+ public:
+    DicNodeProximityFilter(const ProximityInfoState *const pInfoState,
+            const int pointIndex, const bool exactOnly)
+            : mProximityInfoState(pInfoState), mPointIndex(pointIndex), mExactOnly(exactOnly) {}
+
+    bool isFilteredOut(const int codePoint) const {
+        return !isProximityCodePoint(codePoint);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeProximityFilter);
+
+    const ProximityInfoState *const mProximityInfoState;
+    const int mPointIndex;
+    const bool mExactOnly;
+
+    // TODO: Move to proximity info state
+    bool isProximityCodePoint(const int codePoint) const {
+        if (!mProximityInfoState) {
+            return true;
+        }
+        if (mExactOnly) {
+            return mProximityInfoState->getPrimaryCodePointAt(mPointIndex) == codePoint;
+        }
+        const ProximityType matchedId = mProximityInfoState->getProximityType(
+                mPointIndex, codePoint, true /* checkProximityChars */);
+        return ProximityInfoUtils::isMatchOrProximityChar(matchedId);
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PROXIMITY_FILTER_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
index 2a81c3c..2ca4f21 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
@@ -21,6 +21,8 @@
 
 namespace latinime {
 
+class DicNode;
+
 class DicNodeReleaseListener {
  public:
     DicNodeReleaseListener() {}
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
deleted file mode 100644
index 1d4f50a..0000000
--- a/native/jni/src/suggest/core/dicnode/dic_node_state_output.h
+++ /dev/null
@@ -1,75 +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_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_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 5357c37..6b4ef2f 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -14,16 +14,18 @@
  * limitations under the License.
  */
 
-#include <cstring>
-#include <vector>
+#include "suggest/core/dicnode/dic_node_utils.h"
 
-#include "binary_format.h"
-#include "dic_node.h"
-#include "dic_node_utils.h"
-#include "dic_node_vector.h"
-#include "multi_bigram_map.h"
-#include "proximity_info.h"
-#include "proximity_info_state.h"
+#include <cstring>
+
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_proximity_filter.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/dictionary/probability_utils.h"
+#include "suggest/core/policy/dictionary_structure_policy.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -31,22 +33,17 @@
 // 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::initAsRoot(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int prevWordNodePos, DicNode *const newRootNode) {
+    newRootNode->initAsRoot(binaryDictionaryInfo->getStructurePolicy()->getRootPosition(),
+            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::initAsRootWithPreviousWord(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        DicNode *const prevWordLastNode, DicNode *const newRootNode) {
+    newRootNode->initAsRootWithPreviousWord(
+            prevWordLastNode, binaryDictionaryInfo->getStructurePolicy()->getRootPosition());
 }
 
 /* static */ void DicNodeUtils::initByCopy(DicNode *srcNode, DicNode *destNode) {
@@ -58,130 +55,35 @@
 ///////////////////////////////////
 
 /* static */ void DicNodeUtils::createAndGetPassingChildNode(DicNode *dicNode,
-        const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
+        const DicNodeProximityFilter *const childrenFilter,
         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)) {
+    const int baseLowerCaseCodePoint = CharUtils::toBaseLowerCase(codePoint);
+    if (!childrenFilter->isFilteredOut(codePoint)
+            || CharUtils::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::getAllChildDicNodes(DicNode *dicNode,
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, DicNodeVector *childDicNodes) {
+    getProximityChildDicNodes(dicNode, binaryDictionaryInfo, 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) {
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const ProximityInfoState *pInfoState, const int pointIndex, bool exactOnly,
+        DicNodeVector *childDicNodes) {
     if (dicNode->isTotalInputSizeExceedingLimit()) {
         return;
     }
+    const DicNodeProximityFilter childrenFilter(pInfoState, pointIndex, exactOnly);
     if (!dicNode->isLeavingNode()) {
-        DicNodeUtils::createAndGetPassingChildNode(dicNode, pInfoState, pointIndex, exactOnly,
-                childDicNodes);
+        DicNodeUtils::createAndGetPassingChildNode(dicNode, &childrenFilter, childDicNodes);
     } else {
-        DicNodeUtils::createAndGetAllLeavingChildNodes(dicNode, dicRoot, pInfoState, pointIndex,
-                exactOnly, 0 /* codePointsFilter */, 0 /* pInfo */,
-                childDicNodes);
+        binaryDictionaryInfo->getStructurePolicy()->createAndGetAllChildNodes(dicNode,
+                binaryDictionaryInfo, &childrenFilter, childDicNodes);
     }
 }
 
@@ -191,49 +93,35 @@
 /**
  * Computes the combined bigram / unigram cost for the given dicNode.
  */
-/* static */ float DicNodeUtils::getBigramNodeImprobability(const uint8_t *const dicRoot,
+/* static */ float DicNodeUtils::getBigramNodeImprobability(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const DicNode *const node, MultiBigramMap *multiBigramMap) {
-    if (node->isImpossibleBigramWord()) {
+    if (node->hasMultipleWords() && !node->isValidMultipleWordSuggestion()) {
         return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
     }
-    const int probability = getBigramNodeProbability(dicRoot, node, multiBigramMap);
+    const int probability = getBigramNodeProbability(binaryDictionaryInfo, node, multiBigramMap);
     // TODO: This equation to calculate the improbability looks unreasonable.  Investigate this.
     const float cost = static_cast<float>(MAX_PROBABILITY - probability)
             / static_cast<float>(MAX_PROBABILITY);
     return cost;
 }
 
-/* static */ int DicNodeUtils::getBigramNodeProbability(const uint8_t *const dicRoot,
+/* static */ int DicNodeUtils::getBigramNodeProbability(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const DicNode *const node, MultiBigramMap *multiBigramMap) {
     const int unigramProbability = node->getProbability();
     const int wordPos = node->getPos();
     const int prevWordPos = node->getPrevWordPos();
-    if (NOT_VALID_WORD == wordPos || NOT_VALID_WORD == prevWordPos) {
-        // Note: Normally wordPos comes from the dictionary and should never equal NOT_VALID_WORD.
-        return backoff(unigramProbability);
+    if (NOT_A_VALID_WORD_POS == wordPos || NOT_A_VALID_WORD_POS == prevWordPos) {
+        // Note: Normally wordPos comes from the dictionary and should never equal
+        // NOT_A_VALID_WORD_POS.
+        return ProbabilityUtils::backoff(unigramProbability);
     }
     if (multiBigramMap) {
         return multiBigramMap->getBigramProbability(
-                dicRoot, prevWordPos, wordPos, unigramProbability);
+                binaryDictionaryInfo, prevWordPos, wordPos, unigramProbability);
     }
-    return BinaryFormat::getBigramProbability(dicRoot, prevWordPos, wordPos, unigramProbability);
-}
-
-///////////////////////////////////////
-// Bigram / Unigram dictionary utils //
-///////////////////////////////////////
-
-/* 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);
+    return ProbabilityUtils::backoff(unigramProbability);
 }
 
 ////////////////
@@ -262,7 +150,7 @@
         }
         actualLength1 = i + 1;
     }
-    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0 - 1);
+    actualLength1 = min(actualLength1, MAX_WORD_LENGTH - actualLength0);
     memcpy(&dest[actualLength0], src1, actualLength1 * sizeof(dest[0]));
     return actualLength0 + actualLength1;
 }
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
index 5bc542d..4f12b29 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -18,15 +18,15 @@
 #define LATINIME_DIC_NODE_UTILS_H
 
 #include <stdint.h>
-#include <vector>
 
 #include "defines.h"
 
 namespace latinime {
 
+class BinaryDictionaryInfo;
 class DicNode;
+class DicNodeProximityFilter;
 class DicNodeVector;
-class ProximityInfo;
 class ProximityInfoState;
 class MultiBigramMap;
 
@@ -34,48 +34,30 @@
  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,
+    static void initAsRoot(const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const int prevWordNodePos, DicNode *newRootNode);
-    static void initAsRootWithPreviousWord(const int rootPos, const uint8_t *const dicRoot,
+    static void initAsRootWithPreviousWord(const BinaryDictionaryInfo *const binaryDictionaryInfo,
             DicNode *prevWordLastNode, DicNode *newRootNode);
     static void initByCopy(DicNode *srcNode, DicNode *destNode);
-    static void getAllChildDicNodes(DicNode *dicNode, const uint8_t *const dicRoot,
-            DicNodeVector *childDicNodes);
-    static float getBigramNodeImprobability(const uint8_t *const dicRoot,
+    static void getAllChildDicNodes(DicNode *dicNode,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, DicNodeVector *childDicNodes);
+    static float getBigramNodeImprobability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const DicNode *const node, MultiBigramMap *const multiBigramMap);
-    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,
+    static void getProximityChildDicNodes(DicNode *dicNode,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
             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 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,
-            MultiBigramMap *multiBigramMap);
-    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);
-
-    // TODO: Move to proximity info
-    static bool isMatchedNodeCodePoint(const ProximityInfoState *pInfoState, const int pointIndex,
-            const bool exactOnly, const int nodeCodePoint);
+    static int getBigramNodeProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const DicNode *const node, MultiBigramMap *multiBigramMap);
+    static void createAndGetPassingChildNode(DicNode *dicNode,
+            const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes);
 };
 } // 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
index ca07eda..42addae 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -20,7 +20,7 @@
 #include <vector>
 
 #include "defines.h"
-#include "dic_node.h"
+#include "suggest/core/dicnode/dic_node.h"
 
 namespace latinime {
 
@@ -62,17 +62,15 @@
         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) {
+    void pushLeavingChild(const DicNode *const dicNode, const int pos, const int childrenPos,
+            const int probability, const bool isTerminal, const bool hasChildren,
+            const bool isBlacklistedOrNotAWord, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
         ASSERT(!mLock);
         mDicNodes.push_back(mEmptyNode);
-        mDicNodes.back().initAsChild(dicNode, pos, flags, childrenPos, attributesPos, siblingPos,
-                nodeCodePoint, childrenCount, probability, -1 /* bigramProbability */, isTerminal,
-                hasMultipleChars, hasChildren, additionalSubwordLength, additionalSubword);
+        mDicNodes.back().initAsChild(dicNode, pos, childrenPos, probability, isTerminal,
+                hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
     }
 
     DicNode *operator[](const int id) {
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
index b9a6078..c3d2a2e 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.cpp
@@ -17,9 +17,9 @@
 #include <list>
 
 #include "defines.h"
-#include "dic_node_priority_queue.h"
-#include "dic_node_utils.h"
-#include "dic_nodes_cache.h"
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dicnode/dic_nodes_cache.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index a62aa42..7aab090 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -20,13 +20,7 @@
 #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
+#include "suggest/core/dicnode/dic_node_priority_queue.h"
 
 namespace latinime {
 
@@ -38,11 +32,12 @@
 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]),
+            : mActiveDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_ACTIVE]),
+              mNextActiveDicNodes(&mDicNodePriorityQueues[
+                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_NEXT_ACTIVE]),
+              mTerminalDicNodes(&mDicNodePriorityQueues[DIC_NODES_CACHE_INITIAL_QUEUE_ID_TERMINAL]),
+              mCachedDicNodesForContinuousSuggestion(&mDicNodePriorityQueues[
+                      DIC_NODES_CACHE_INITIAL_QUEUE_ID_CACHE_FOR_CONTINUOUS_SUGGESTION]),
               mInputIndex(0), mLastCachedInputIndex(0) {
     }
 
@@ -147,9 +142,8 @@
             mCachedDicNodesForContinuousSuggestion->dump();
         }
         mInputIndex = mLastCachedInputIndex;
-        mCachedDicNodesForContinuousSuggestion =
-                moveNodesAndReturnReusableEmptyQueue(
-                        mCachedDicNodesForContinuousSuggestion, &mActiveDicNodes);
+        mCachedDicNodesForContinuousSuggestion = moveNodesAndReturnReusableEmptyQueue(
+                mCachedDicNodesForContinuousSuggestion, &mActiveDicNodes);
     }
 
     AK_FORCE_INLINE static DicNodePriorityQueue *moveNodesAndReturnReusableEmptyQueue(
@@ -169,7 +163,7 @@
         mTerminalDicNodes->clear();
     }
 
-    DicNodePriorityQueue mDicNodePriorityQueues[PRIORITY_QUEUES_SIZE];
+    DicNodePriorityQueue mDicNodePriorityQueues[DIC_NODES_CACHE_PRIORITY_QUEUES_SIZE];
     // Active dicNodes currently being expanded.
     DicNodePriorityQueue *mActiveDicNodes;
     // Next dicNodes to be expanded.
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
new file mode 100644
index 0000000..9e0f62c
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_properties.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_PROPERTIES_H
+#define LATINIME_DIC_NODE_PROPERTIES_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+/**
+ * Node for traversing the lexicon trie.
+ */
+// TODO: Introduce a dictionary node class which has attribute members required to understand the
+// dictionary structure.
+class DicNodeProperties {
+ public:
+    AK_FORCE_INLINE DicNodeProperties()
+            : mPos(0), mChildrenPos(0), mProbability(0), mNodeCodePoint(0), mIsTerminal(false),
+              mHasChildren(false), mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
+
+    virtual ~DicNodeProperties() {}
+
+    // Should be called only once per DicNode is initialized.
+    void init(const int pos, const int childrenPos, const int nodeCodePoint, const int probability,
+            const bool isTerminal, const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t depth, const uint16_t leavingDepth) {
+        mPos = pos;
+        mChildrenPos = childrenPos;
+        mNodeCodePoint = nodeCodePoint;
+        mProbability = probability;
+        mIsTerminal = isTerminal;
+        mHasChildren = hasChildren;
+        mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
+        mDepth = depth;
+        mLeavingDepth = leavingDepth;
+    }
+
+    // Init for copy
+    void init(const DicNodeProperties *const nodeProp) {
+        mPos = nodeProp->mPos;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mNodeCodePoint = nodeProp->mNodeCodePoint;
+        mProbability = nodeProp->mProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = nodeProp->mDepth;
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    // Init as passing child
+    void init(const DicNodeProperties *const nodeProp, const int codePoint) {
+        mPos = nodeProp->mPos;
+        mChildrenPos = nodeProp->mChildrenPos;
+        mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
+        mProbability = nodeProp->mProbability;
+        mIsTerminal = nodeProp->mIsTerminal;
+        mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
+        mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
+        mLeavingDepth = nodeProp->mLeavingDepth;
+    }
+
+    int getPos() const {
+        return mPos;
+    }
+
+    int getChildrenPos() const {
+        return mChildrenPos;
+    }
+
+    int getProbability() const {
+        return mProbability;
+    }
+
+    int getNodeCodePoint() const {
+        return mNodeCodePoint;
+    }
+
+    uint16_t getDepth() const {
+        return mDepth;
+    }
+
+    // TODO: Move to output?
+    uint16_t getLeavingDepth() const {
+        return mLeavingDepth;
+    }
+
+    bool isTerminal() const {
+        return mIsTerminal;
+    }
+
+    bool hasChildren() const {
+        return mHasChildren || mDepth != mLeavingDepth;
+    }
+
+    bool isBlacklistedOrNotAWord() const {
+        return mIsBlacklistedOrNotAWord;
+    }
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    int mPos;
+    int mChildrenPos;
+    int mProbability;
+    int mNodeCodePoint;
+    bool mIsTerminal;
+    bool mHasChildren;
+    bool mIsBlacklistedOrNotAWord;
+    uint16_t mDepth;
+    uint16_t mLeavingDepth;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PROPERTIES_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h
similarity index 77%
rename from native/jni/src/suggest/core/dicnode/dic_node_state.h
rename to native/jni/src/suggest/core/dicnode/internal/dic_node_state.h
index 239b63c..b0fddb7 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state.h
@@ -18,10 +18,10 @@
 #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"
+#include "suggest/core/dicnode/internal/dic_node_state_input.h"
+#include "suggest/core/dicnode/internal/dic_node_state_output.h"
+#include "suggest/core/dicnode/internal/dic_node_state_prevword.h"
+#include "suggest/core/dicnode/internal/dic_node_state_scoring.h"
 
 namespace latinime {
 
@@ -55,11 +55,12 @@
         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 by copy and adding merged node code points.
+    void init(const DicNodeState *const src, const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
         init(src);
-        mDicNodeStateOutput.addSubword(additionalSubwordLength, additionalSubword);
+        mDicNodeStateOutput.addMergedNodeCodePoints(
+                mergedNodeCodePointCount, mergedNodeCodePoints);
     }
 
  private:
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_input.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h
similarity index 100%
rename from native/jni/src/suggest/core/dicnode/dic_node_state_input.h
rename to native/jni/src/suggest/core/dicnode/internal/dic_node_state_input.h
diff --git a/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
new file mode 100644
index 0000000..45c7f5c
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_output.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_STATE_OUTPUT_H
+#define LATINIME_DIC_NODE_STATE_OUTPUT_H
+
+#include <cstring> // for memcpy()
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class DicNodeStateOutput {
+ public:
+    DicNodeStateOutput() : mOutputtedCodePointCount(0) {
+        init();
+    }
+
+    virtual ~DicNodeStateOutput() {}
+
+    void init() {
+        mOutputtedCodePointCount = 0;
+        mCodePointsBuf[0] = 0;
+    }
+
+    void init(const DicNodeStateOutput *const stateOutput) {
+        memcpy(mCodePointsBuf, stateOutput->mCodePointsBuf,
+                stateOutput->mOutputtedCodePointCount * sizeof(mCodePointsBuf[0]));
+        mOutputtedCodePointCount = stateOutput->mOutputtedCodePointCount;
+        if (mOutputtedCodePointCount < MAX_WORD_LENGTH) {
+            mCodePointsBuf[mOutputtedCodePointCount] = 0;
+        }
+    }
+
+    void addMergedNodeCodePoints(const uint16_t mergedNodeCodePointCount,
+            const int *const mergedNodeCodePoints) {
+        if (mergedNodeCodePoints) {
+            memcpy(&mCodePointsBuf[mOutputtedCodePointCount], mergedNodeCodePoints,
+                    mergedNodeCodePointCount * sizeof(mCodePointsBuf[0]));
+            mOutputtedCodePointCount = static_cast<uint16_t>(
+                    mOutputtedCodePointCount + mergedNodeCodePointCount);
+            if (mOutputtedCodePointCount < MAX_WORD_LENGTH) {
+                mCodePointsBuf[mOutputtedCodePointCount] = 0;
+            }
+        }
+    }
+
+    // TODO: Remove
+    int getCodePointAt(const int index) const {
+        return mCodePointsBuf[index];
+    }
+
+    // TODO: Move to private
+    int mCodePointsBuf[MAX_WORD_LENGTH];
+
+ private:
+    // Caution!!!
+    // Use a default copy constructor and an assign operator because shallow copies are ok
+    // for this class
+    uint16_t mOutputtedCodePointCount;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_STATE_OUTPUT_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
similarity index 96%
rename from native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
rename to native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
index e3b892b..5854f4f 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state_prevword.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_prevword.h
@@ -21,7 +21,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "dic_node_utils.h"
+#include "suggest/core/dicnode/dic_node_utils.h"
 
 namespace latinime {
 
@@ -29,7 +29,7 @@
  public:
     AK_FORCE_INLINE DicNodeStatePrevWord()
             : mPrevWordCount(0), mPrevWordLength(0), mPrevWordStart(0), mPrevWordProbability(0),
-              mPrevWordNodePos(0) {
+              mPrevWordNodePos(NOT_A_VALID_WORD_POS) {
         memset(mPrevWord, 0, sizeof(mPrevWord));
         memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
     }
@@ -41,7 +41,7 @@
         mPrevWordCount = 0;
         mPrevWordStart = 0;
         mPrevWordProbability = -1;
-        mPrevWordNodePos = NOT_VALID_WORD;
+        mPrevWordNodePos = NOT_A_VALID_WORD_POS;
         memset(mPrevSpacePositions, 0, sizeof(mPrevSpacePositions));
     }
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
similarity index 98%
rename from native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
rename to native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
index dca9d60..4c88422 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_state_scoring.h
+++ b/native/jni/src/suggest/core/dicnode/internal/dic_node_state_scoring.h
@@ -20,7 +20,7 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "digraph_utils.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 
 namespace latinime {
 
diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
similarity index 60%
rename from native/jni/src/bigram_dictionary.cpp
rename to native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
index 9053e72..532c769 100644
--- a/native/jni/src/bigram_dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp
@@ -19,15 +19,18 @@
 #define LOG_TAG "LatinIME: bigram_dictionary.cpp"
 
 #include "bigram_dictionary.h"
-#include "binary_format.h"
-#include "bloom_filter.h"
-#include "char_utils.h"
+
 #include "defines.h"
-#include "dictionary.h"
+#include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/probability_utils.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
-BigramDictionary::BigramDictionary(const uint8_t *const streamStart) : DICT_ROOT(streamStart) {
+BigramDictionary::BigramDictionary(const BinaryDictionaryInfo *const binaryDictionaryInfo)
+        : mBinaryDictionaryInfo(binaryDictionaryInfo) {
     if (DEBUG_DICT) {
         AKLOGI("BigramDictionary - constructor");
     }
@@ -51,7 +54,7 @@
     int insertAt = 0;
     while (insertAt < MAX_RESULTS) {
         if (probability > bigramProbability[insertAt] || (bigramProbability[insertAt] == probability
-                && length < getCodePointCount(MAX_WORD_LENGTH,
+                && length < CharUtils::getCodePointCount(MAX_WORD_LENGTH,
                         bigramCodePoints + insertAt * MAX_WORD_LENGTH))) {
             break;
         }
@@ -97,97 +100,61 @@
  * and the bigrams are used to boost unigram result scores, it makes little sense to
  * reduce their scope to the ones that match the first letter.
  */
-int BigramDictionary::getBigrams(const int *prevWord, int prevWordLength, int *inputCodePoints,
+int BigramDictionary::getPredictions(const int *prevWord, int prevWordLength, int *inputCodePoints,
         int inputSize, int *bigramCodePoints, int *bigramProbability, int *outputTypes) const {
     // TODO: remove unused arguments, and refrain from storing stuff in members of this class
     // TODO: have "in" arguments before "out" ones, and make out args explicit in the name
 
-    const uint8_t *const root = DICT_ROOT;
     int pos = getBigramListPositionForWord(prevWord, prevWordLength,
             false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
-    if (0 == pos) {
+    if (NOT_A_DICT_POS == pos) {
         // If no bigrams for this exact word, search again in lower case.
         pos = getBigramListPositionForWord(prevWord, prevWordLength,
                 true /* forceLowerCaseSearch */);
     }
     // If still no bigrams, we really don't have them!
-    if (0 == pos) return 0;
-    uint8_t bigramFlags;
+    if (NOT_A_DICT_POS == pos) return 0;
+
     int bigramCount = 0;
-    do {
-        bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-        int bigramBuffer[MAX_WORD_LENGTH];
-        int unigramProbability = 0;
-        const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
-                &pos);
-        const int length = BinaryFormat::getWordAtAddress(root, bigramPos, MAX_WORD_LENGTH,
-                bigramBuffer, &unigramProbability);
+    int unigramProbability = 0;
+    int bigramBuffer[MAX_WORD_LENGTH];
+    BinaryDictionaryBigramsIterator bigramsIt(mBinaryDictionaryInfo, pos);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        const int length = mBinaryDictionaryInfo->getStructurePolicy()->
+                getCodePointsAndProbabilityAndReturnCodePointCount(
+                        mBinaryDictionaryInfo, bigramsIt.getBigramPos(), MAX_WORD_LENGTH,
+                        bigramBuffer, &unigramProbability);
 
         // inputSize == 0 means we are trying to find bigram predictions.
         if (inputSize < 1 || checkFirstCharacter(bigramBuffer, inputCodePoints)) {
-            const int bigramProbabilityTemp =
-                    BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
+            const int bigramProbabilityTemp = bigramsIt.getProbability();
             // Due to space constraints, the probability for bigrams is approximate - the lower the
             // unigram probability, the worse the precision. The theoritical maximum error in
             // resulting probability is 8 - although in the practice it's never bigger than 3 or 4
             // in very bad cases. This means that sometimes, we'll see some bigrams interverted
             // here, but it can't get too bad.
-            const int probability = BinaryFormat::computeProbabilityForBigram(
+            const int probability = ProbabilityUtils::computeProbabilityForBigram(
                     unigramProbability, bigramProbabilityTemp);
             addWordBigram(bigramBuffer, length, probability, bigramProbability, bigramCodePoints,
                     outputTypes);
             ++bigramCount;
         }
-    } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
+    }
     return min(bigramCount, MAX_RESULTS);
 }
 
 // Returns a pointer to the start of the bigram list.
-// If the word is not found or has no bigrams, this function returns 0.
+// If the word is not found or has no bigrams, this function returns NOT_A_DICT_POS.
 int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
         const bool forceLowerCaseSearch) const {
-    if (0 >= prevWordLength) return 0;
-    const uint8_t *const root = DICT_ROOT;
-    int pos = BinaryFormat::getTerminalPosition(root, prevWord, prevWordLength,
-            forceLowerCaseSearch);
-
-    if (NOT_VALID_WORD == pos) return 0;
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-    if (0 == (flags & BinaryFormat::FLAG_HAS_BIGRAMS)) return 0;
-    if (0 == (flags & BinaryFormat::FLAG_HAS_MULTIPLE_CHARS)) {
-        BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-    } else {
-        pos = BinaryFormat::skipOtherCharacters(root, pos);
-    }
-    pos = BinaryFormat::skipProbability(flags, pos);
-    pos = BinaryFormat::skipChildrenPosition(flags, pos);
-    pos = BinaryFormat::skipShortcuts(root, flags, pos);
-    return pos;
-}
-
-void BigramDictionary::fillBigramAddressToProbabilityMapAndFilter(const int *prevWord,
-        const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const {
-    memset(filter, 0, BIGRAM_FILTER_BYTE_SIZE);
-    const uint8_t *const root = DICT_ROOT;
-    int pos = getBigramListPositionForWord(prevWord, prevWordLength,
-            false /* forceLowerCaseSearch */);
-    if (0 == pos) {
-        // If no bigrams for this exact string, search again in lower case.
-        pos = getBigramListPositionForWord(prevWord, prevWordLength,
-                true /* forceLowerCaseSearch */);
-    }
-    if (0 == pos) return;
-
-    uint8_t bigramFlags;
-    do {
-        bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-        const int probability = BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags;
-        const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
-                &pos);
-        (*map)[bigramPos] = probability;
-        setInFilter(filter, bigramPos);
-    } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
+    if (0 >= prevWordLength) return NOT_A_DICT_POS;
+    int pos = mBinaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
+            mBinaryDictionaryInfo, prevWord, prevWordLength, forceLowerCaseSearch);
+    if (NOT_A_VALID_WORD_POS == pos) return NOT_A_DICT_POS;
+    return mBinaryDictionaryInfo->getStructurePolicy()->getBigramsPositionOfNode(
+            mBinaryDictionaryInfo, pos);
 }
 
 bool BigramDictionary::checkFirstCharacter(int *word, int *inputCodePoints) const {
@@ -195,9 +162,9 @@
     // what user typed.
 
     int maxAlt = MAX_ALTERNATIVES;
-    const int firstBaseLowerCodePoint = toBaseLowerCase(*word);
+    const int firstBaseLowerCodePoint = CharUtils::toBaseLowerCase(*word);
     while (maxAlt > 0) {
-        if (toBaseLowerCase(*inputCodePoints) == firstBaseLowerCodePoint) {
+        if (CharUtils::toBaseLowerCase(*inputCodePoints) == firstBaseLowerCodePoint) {
             return true;
         }
         inputCodePoints++;
@@ -206,24 +173,22 @@
     return false;
 }
 
-bool BigramDictionary::isValidBigram(const int *word1, int length1, const int *word2,
-        int length2) const {
-    const uint8_t *const root = DICT_ROOT;
-    int pos = getBigramListPositionForWord(word1, length1, false /* forceLowerCaseSearch */);
+bool BigramDictionary::isValidBigram(const int *word0, int length0, const int *word1,
+        int length1) const {
+    int pos = getBigramListPositionForWord(word0, length0, false /* forceLowerCaseSearch */);
     // getBigramListPositionForWord returns 0 if this word isn't in the dictionary or has no bigrams
-    if (0 == pos) return false;
-    int nextWordPos = BinaryFormat::getTerminalPosition(root, word2, length2,
-            false /* forceLowerCaseSearch */);
-    if (NOT_VALID_WORD == nextWordPos) return false;
-    uint8_t bigramFlags;
-    do {
-        bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-        const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags,
-                &pos);
-        if (bigramPos == nextWordPos) {
+    if (NOT_A_DICT_POS == pos) return false;
+    int nextWordPos = mBinaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
+            mBinaryDictionaryInfo, word1, length1, false /* forceLowerCaseSearch */);
+    if (NOT_A_VALID_WORD_POS == nextWordPos) return false;
+
+    BinaryDictionaryBigramsIterator bigramsIt(mBinaryDictionaryInfo, pos);
+    while (bigramsIt.hasNext()) {
+        bigramsIt.next();
+        if (bigramsIt.getBigramPos() == nextWordPos) {
             return true;
         }
-    } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags);
+    }
     return false;
 }
 
diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
similarity index 76%
rename from native/jni/src/bigram_dictionary.h
rename to native/jni/src/suggest/core/dictionary/bigram_dictionary.h
index b86e564..7706a2c 100644
--- a/native/jni/src/bigram_dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h
@@ -17,31 +17,31 @@
 #ifndef LATINIME_BIGRAM_DICTIONARY_H
 #define LATINIME_BIGRAM_DICTIONARY_H
 
-#include <map>
-#include <stdint.h>
-
 #include "defines.h"
 
 namespace latinime {
 
+class BinaryDictionaryInfo;
+
 class BigramDictionary {
  public:
-    BigramDictionary(const uint8_t *const streamStart);
-    int getBigrams(const int *word, int length, int *inputCodePoints, int inputSize, int *outWords,
-            int *frequencies, int *outputTypes) const;
-    void fillBigramAddressToProbabilityMapAndFilter(const int *prevWord, const int prevWordLength,
-            std::map<int, int> *map, uint8_t *filter) const;
+    BigramDictionary(const BinaryDictionaryInfo *const binaryDictionaryInfo);
+
+    int getPredictions(const int *word, int length, int *inputCodePoints, int inputSize,
+            int *outWords, int *frequencies, int *outputTypes) const;
     bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const;
     ~BigramDictionary();
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictionary);
+
     void addWordBigram(int *word, int length, int probability, int *bigramProbability,
             int *bigramCodePoints, int *outputTypes) const;
     bool checkFirstCharacter(int *word, int *inputCodePoints) const;
     int getBigramListPositionForWord(const int *prevWord, const int prevWordLength,
             const bool forceLowerCaseSearch) const;
 
-    const uint8_t *const DICT_ROOT;
+    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
     // TODO: Re-implement proximity correction for bigram correction
     static const int MAX_ALTERNATIVES = 1;
 };
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
new file mode 100644
index 0000000..8cbb129
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_bigrams_iterator.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
+#define LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+
+namespace latinime {
+
+class BinaryDictionaryBigramsIterator {
+ public:
+    BinaryDictionaryBigramsIterator(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int pos)
+            : mBinaryDictionaryInfo(binaryDictionaryInfo), mPos(pos), mBigramFlags(0),
+              mBigramPos(NOT_A_DICT_POS), mHasNext(pos != NOT_A_DICT_POS) {}
+
+    AK_FORCE_INLINE bool hasNext() const {
+        return mHasNext;
+    }
+
+    AK_FORCE_INLINE void next() {
+        mBigramFlags = BinaryDictionaryTerminalAttributesReadingUtils::getFlagsAndForwardPointer(
+                mBinaryDictionaryInfo, &mPos);
+        mBigramPos =
+                BinaryDictionaryTerminalAttributesReadingUtils::getBigramAddressAndForwardPointer(
+                        mBinaryDictionaryInfo, mBigramFlags, &mPos);
+        mHasNext = BinaryDictionaryTerminalAttributesReadingUtils::hasNext(mBigramFlags);
+    }
+
+    AK_FORCE_INLINE int getProbability() const {
+        return BinaryDictionaryTerminalAttributesReadingUtils::getProbabilityFromFlags(
+                mBigramFlags);
+    }
+
+    AK_FORCE_INLINE int getBigramPos() const {
+        return mBigramPos;
+    }
+
+    AK_FORCE_INLINE int getFlags() const {
+        return mBigramFlags;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryBigramsIterator);
+
+    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+    int mPos;
+    BinaryDictionaryTerminalAttributesReadingUtils::BigramFlags mBigramFlags;
+    int mBigramPos;
+    bool mHasNext;
+};
+} // namespace latinime
+#endif // LATINIME_BINARY_DICTIONARY_BIGRAMS_ITERATOR_H
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
new file mode 100644
index 0000000..5d14a05
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
+
+namespace latinime {
+
+/**
+ * Dictionary size
+ */
+// Any file smaller than this is not a dictionary.
+const int BinaryDictionaryFormatUtils::DICTIONARY_MINIMUM_SIZE = 4;
+
+/**
+ * Format versions
+ */
+
+// The versions of Latin IME that only handle format version 1 only test for the magic
+// number, so we had to change it so that version 2 files would be rejected by older
+// implementations. On this occasion, we made the magic number 32 bits long.
+const uint32_t BinaryDictionaryFormatUtils::HEADER_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+// Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
+const int BinaryDictionaryFormatUtils::HEADER_VERSION_2_MINIMUM_SIZE = 12;
+
+/* static */ BinaryDictionaryFormatUtils::FORMAT_VERSION
+        BinaryDictionaryFormatUtils::detectFormatVersion(const uint8_t *const dict,
+                const int dictSize) {
+    // The magic number is stored big-endian.
+    // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't
+    // understand this format.
+    if (dictSize < DICTIONARY_MINIMUM_SIZE) {
+        return UNKNOWN_VERSION;
+    }
+    const uint32_t magicNumber = ByteArrayUtils::readUint32(dict, 0);
+    switch (magicNumber) {
+        case HEADER_VERSION_2_MAGIC_NUMBER:
+            // Version 2 header are at least 12 bytes long.
+            // If this header has the version 2 magic number but is less than 12 bytes long,
+            // then it's an unknown format and we need to avoid confidently reading the next bytes.
+            if (dictSize < HEADER_VERSION_2_MINIMUM_SIZE) {
+                return UNKNOWN_VERSION;
+            }
+            // Version 2 header is as follows:
+            // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
+            // Version number (2 bytes)
+            // Options (2 bytes)
+            // Header size (4 bytes) : integer, big endian
+            if (ByteArrayUtils::readUint16(dict, 4) == 2) {
+                return VERSION_2;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == 3) {
+                // TODO: Support version 3 dictionary.
+                return UNKNOWN_VERSION;
+            } else {
+                return UNKNOWN_VERSION;
+            }
+        default:
+            return UNKNOWN_VERSION;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
new file mode 100644
index 0000000..830684c
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H
+#define LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+/**
+ * Methods to handle binary dictionary format version.
+ *
+ * Currently, we have a file with a similar name, binary_format.h. binary_format.h contains binary
+ * reading methods and utility methods for various purposes.
+ * On the other hand, this file deals with only about dictionary format version.
+ */
+class BinaryDictionaryFormatUtils {
+ public:
+    enum FORMAT_VERSION {
+        VERSION_2,
+        VERSION_3,
+        UNKNOWN_VERSION
+    };
+
+    static FORMAT_VERSION detectFormatVersion(const uint8_t *const dict, const int dictSize);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryFormatUtils);
+
+    static const int DICTIONARY_MINIMUM_SIZE;
+    static const uint32_t HEADER_VERSION_2_MAGIC_NUMBER;
+    static const int HEADER_VERSION_2_MINIMUM_SIZE;
+};
+} // namespace latinime
+#endif /* LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp
new file mode 100644
index 0000000..91c643a
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/binary_dictionary_header.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+
+namespace latinime {
+
+const char *const BinaryDictionaryHeader::MULTIPLE_WORDS_DEMOTION_RATE_KEY =
+        "MULTIPLE_WORDS_DEMOTION_RATE";
+const float BinaryDictionaryHeader::DEFAULT_MULTI_WORD_COST_MULTIPLIER = 1.0f;
+const float BinaryDictionaryHeader::MULTI_WORD_COST_MULTIPLIER_SCALE = 100.0f;
+
+BinaryDictionaryHeader::BinaryDictionaryHeader(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo)
+        : mBinaryDictionaryInfo(binaryDictionaryInfo),
+          mDictionaryFlags(BinaryDictionaryHeaderReadingUtils::getFlags(binaryDictionaryInfo)),
+          mSize(BinaryDictionaryHeaderReadingUtils::getHeaderSize(binaryDictionaryInfo)),
+          mMultiWordCostMultiplier(readMultiWordCostMultiplier()) {}
+
+float BinaryDictionaryHeader::readMultiWordCostMultiplier() const {
+    const int headerValue = BinaryDictionaryHeaderReadingUtils::readHeaderValueInt(
+            mBinaryDictionaryInfo, MULTIPLE_WORDS_DEMOTION_RATE_KEY);
+    if (headerValue == S_INT_MIN) {
+        // not found
+        return DEFAULT_MULTI_WORD_COST_MULTIPLIER;
+    }
+    if (headerValue <= 0) {
+        return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
+    }
+    return MULTI_WORD_COST_MULTIPLIER_SCALE / static_cast<float>(headerValue);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
new file mode 100644
index 0000000..240512b
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_HEADER_H
+#define LATINIME_BINARY_DICTIONARY_HEADER_H
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_header_reading_utils.h"
+
+namespace latinime {
+
+class BinaryDictionaryInfo;
+
+/**
+ * This class abstracts dictionary header structures and provide interface to access dictionary
+ * header information.
+ */
+class BinaryDictionaryHeader {
+ public:
+    explicit BinaryDictionaryHeader(const BinaryDictionaryInfo *const binaryDictionaryInfo);
+
+    AK_FORCE_INLINE int getSize() const {
+        return mSize;
+    }
+
+    AK_FORCE_INLINE bool supportsDynamicUpdate() const {
+        return BinaryDictionaryHeaderReadingUtils::supportsDynamicUpdate(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const {
+        return BinaryDictionaryHeaderReadingUtils::requiresGermanUmlautProcessing(mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const {
+        return BinaryDictionaryHeaderReadingUtils::requiresFrenchLigatureProcessing(
+                mDictionaryFlags);
+    }
+
+    AK_FORCE_INLINE float getMultiWordCostMultiplier() const {
+        return mMultiWordCostMultiplier;
+    }
+
+    AK_FORCE_INLINE void readHeaderValueOrQuestionMark(const char *const key,
+            int *outValue, int outValueSize) const {
+        if (outValueSize <= 0) return;
+        if (outValueSize == 1) {
+            outValue[0] = '\0';
+            return;
+        }
+        if (!BinaryDictionaryHeaderReadingUtils::readHeaderValue(mBinaryDictionaryInfo,
+                key, outValue, outValueSize)) {
+            outValue[0] = '?';
+            outValue[1] = '\0';
+        }
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryHeader);
+
+    static const char *const MULTIPLE_WORDS_DEMOTION_RATE_KEY;
+    static const float DEFAULT_MULTI_WORD_COST_MULTIPLIER;
+    static const float MULTI_WORD_COST_MULTIPLIER_SCALE;
+
+    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+    const BinaryDictionaryHeaderReadingUtils::DictionaryFlags mDictionaryFlags;
+    const int mSize;
+    const float mMultiWordCostMultiplier;
+
+    float readMultiWordCostMultiplier() const;
+};
+} // namespace latinime
+#endif // LATINIME_BINARY_DICTIONARY_HEADER_H
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
new file mode 100644
index 0000000..a57b0f8
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/binary_dictionary_header_reading_utils.h"
+
+#include <cctype>
+#include <cstdlib>
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+
+namespace latinime {
+
+const int BinaryDictionaryHeaderReadingUtils::MAX_OPTION_KEY_LENGTH = 256;
+
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_MAGIC_NUMBER_SIZE = 4;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_DICTIONARY_VERSION_SIZE = 2;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_FLAG_SIZE = 2;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_SIZE_FIELD_SIZE = 4;
+
+const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
+        BinaryDictionaryHeaderReadingUtils::NO_FLAGS = 0;
+// Flags for special processing
+// Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or
+// something very bad (like, the apocalypse) will happen. Please update both at the same time.
+const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
+        BinaryDictionaryHeaderReadingUtils::GERMAN_UMLAUT_PROCESSING_FLAG = 0x1;
+const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
+        BinaryDictionaryHeaderReadingUtils::SUPPORTS_DYNAMIC_UPDATE_FLAG = 0x2;
+const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
+        BinaryDictionaryHeaderReadingUtils::FRENCH_LIGATURE_PROCESSING_FLAG = 0x4;
+
+/* static */ int BinaryDictionaryHeaderReadingUtils::getHeaderSize(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo) {
+    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
+        case HEADER_VERSION_2:
+            // See the format of the header in the comment in
+            // BinaryDictionaryFormatUtils::detectFormatVersion()
+            return ByteArrayUtils::readUint32(binaryDictionaryInfo->getDictBuf(),
+                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
+                            + VERSION_2_HEADER_FLAG_SIZE);
+        default:
+            return S_INT_MAX;
+    }
+}
+
+/* static */ BinaryDictionaryHeaderReadingUtils::DictionaryFlags
+        BinaryDictionaryHeaderReadingUtils::getFlags(
+                const BinaryDictionaryInfo *const binaryDictionaryInfo) {
+    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
+        case HEADER_VERSION_2:
+            return ByteArrayUtils::readUint16(binaryDictionaryInfo->getDictBuf(),
+                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE);
+        default:
+            return NO_FLAGS;
+    }
+}
+
+// Returns if the key is found or not and reads the found value into outValue.
+/* static */ bool BinaryDictionaryHeaderReadingUtils::readHeaderValue(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const char *const key, int *outValue, const int outValueSize) {
+    if (outValueSize <= 0) {
+        return false;
+    }
+    const int headerSize = getHeaderSize(binaryDictionaryInfo);
+    int pos = getHeaderOptionsPosition(binaryDictionaryInfo->getFormat());
+    if (pos == NOT_A_DICT_POS) {
+        // The header doesn't have header options.
+        return false;
+    }
+    while (pos < headerSize) {
+        if(ByteArrayUtils::compareStringInBufferWithCharArray(
+                binaryDictionaryInfo->getDictBuf(), key, headerSize - pos, &pos) == 0) {
+            // The key was found.
+            const int length = ByteArrayUtils::readStringAndAdvancePosition(
+                    binaryDictionaryInfo->getDictBuf(), outValueSize, outValue, &pos);
+            // Add a 0 terminator to the string.
+            outValue[length < outValueSize ? length : outValueSize - 1] = '\0';
+            return true;
+        }
+        ByteArrayUtils::advancePositionToBehindString(
+                binaryDictionaryInfo->getDictBuf(), headerSize - pos, &pos);
+    }
+    // The key was not found.
+    return false;
+}
+
+/* static */ int BinaryDictionaryHeaderReadingUtils::readHeaderValueInt(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key) {
+    const int bufferSize = LARGEST_INT_DIGIT_COUNT;
+    int intBuffer[bufferSize];
+    char charBuffer[bufferSize];
+    if (!readHeaderValue(binaryDictionaryInfo, key, intBuffer, bufferSize)) {
+        return S_INT_MIN;
+    }
+    for (int i = 0; i < bufferSize; ++i) {
+        charBuffer[i] = intBuffer[i];
+        if (charBuffer[i] == '0') {
+            break;
+        }
+        if (!isdigit(charBuffer[i])) {
+            // If not a number, return S_INT_MIN
+            return S_INT_MIN;
+        }
+    }
+    return atoi(charBuffer);
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
new file mode 100644
index 0000000..6174822
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.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_DICTIONARY_HEADER_READING_UTILS_H
+#define LATINIME_DICTIONARY_HEADER_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
+
+namespace latinime {
+
+class BinaryDictionaryInfo;
+
+class BinaryDictionaryHeaderReadingUtils {
+ public:
+    typedef uint16_t DictionaryFlags;
+
+    static const int MAX_OPTION_KEY_LENGTH;
+
+    static int getHeaderSize(const BinaryDictionaryInfo *const binaryDictionaryInfo);
+
+    static DictionaryFlags getFlags(const BinaryDictionaryInfo *const binaryDictionaryInfo);
+
+    static AK_FORCE_INLINE bool supportsDynamicUpdate(const DictionaryFlags flags) {
+        return (flags & SUPPORTS_DYNAMIC_UPDATE_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE bool requiresGermanUmlautProcessing(const DictionaryFlags flags) {
+        return (flags & GERMAN_UMLAUT_PROCESSING_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE bool requiresFrenchLigatureProcessing(const DictionaryFlags flags) {
+        return (flags & FRENCH_LIGATURE_PROCESSING_FLAG) != 0;
+    }
+
+    static AK_FORCE_INLINE int getHeaderOptionsPosition(
+            const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
+        switch (getHeaderVersion(dictionaryFormat)) {
+        case HEADER_VERSION_2:
+            return VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
+                    + VERSION_2_HEADER_FLAG_SIZE + VERSION_2_HEADER_SIZE_FIELD_SIZE;
+            break;
+        default:
+            return NOT_A_DICT_POS;
+        }
+    }
+
+    static bool readHeaderValue(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const char *const key, int *outValue, const int outValueSize);
+
+    static int readHeaderValueInt(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryHeaderReadingUtils);
+
+    enum HEADER_VERSION {
+        HEADER_VERSION_2,
+        UNKNOWN_HEADER_VERSION
+    };
+
+    static const int VERSION_2_HEADER_MAGIC_NUMBER_SIZE;
+    static const int VERSION_2_HEADER_DICTIONARY_VERSION_SIZE;
+    static const int VERSION_2_HEADER_FLAG_SIZE;
+    static const int VERSION_2_HEADER_SIZE_FIELD_SIZE;
+
+    static const DictionaryFlags NO_FLAGS;
+    // Flags for special processing
+    // Those *must* match the flags in makedict (FormatSpec#*_PROCESSING_FLAGS) or
+    // something very bad (like, the apocalypse) will happen. Please update both at the same time.
+    static const DictionaryFlags GERMAN_UMLAUT_PROCESSING_FLAG;
+    static const DictionaryFlags SUPPORTS_DYNAMIC_UPDATE_FLAG;
+    static const DictionaryFlags FRENCH_LIGATURE_PROCESSING_FLAG;
+    static const DictionaryFlags CONTAINS_BIGRAMS_FLAG;
+
+    static HEADER_VERSION getHeaderVersion(
+            const BinaryDictionaryFormatUtils::FORMAT_VERSION formatVersion) {
+        switch(formatVersion) {
+            case BinaryDictionaryFormatUtils::VERSION_2:
+                // Fall through
+            case BinaryDictionaryFormatUtils::VERSION_3:
+                return HEADER_VERSION_2;
+            default:
+                return UNKNOWN_HEADER_VERSION;
+        }
+    }
+};
+}
+#endif /* LATINIME_DICTIONARY_HEADER_READING_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
new file mode 100644
index 0000000..cbea18f
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_INFO_H
+#define LATINIME_BINARY_DICTIONARY_INFO_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
+#include "suggest/core/dictionary/binary_dictionary_header.h"
+#include "suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h"
+#include "utils/log_utils.h"
+
+namespace latinime {
+
+class BinaryDictionaryInfo {
+ public:
+     AK_FORCE_INLINE BinaryDictionaryInfo(JNIEnv *env, const uint8_t *const dictBuf,
+            const int dictSize, const int mmapFd, const int dictBufOffset, const bool isUpdatable)
+            : mDictBuf(dictBuf), mDictSize(dictSize), mMmapFd(mmapFd),
+              mDictBufOffset(dictBufOffset), mIsUpdatable(isUpdatable),
+              mDictionaryFormat(BinaryDictionaryFormatUtils::detectFormatVersion(
+                      mDictBuf, mDictSize)),
+              mDictionaryHeader(this), mDictRoot(mDictBuf + mDictionaryHeader.getSize()),
+              mStructurePolicy(DictionaryStructurePolicyFactory::getDictionaryStructurePolicy(
+                      mDictionaryFormat)) {
+        logDictionaryInfo(env);
+    }
+
+    AK_FORCE_INLINE const uint8_t *getDictBuf() const {
+        return mDictBuf;
+    }
+
+    AK_FORCE_INLINE int getDictSize() const {
+        return mDictSize;
+    }
+
+    AK_FORCE_INLINE int getMmapFd() const {
+        return mMmapFd;
+    }
+
+    AK_FORCE_INLINE int getDictBufOffset() const {
+        return mDictBufOffset;
+    }
+
+    AK_FORCE_INLINE const uint8_t *getDictRoot() const {
+        return mDictRoot;
+    }
+
+    AK_FORCE_INLINE BinaryDictionaryFormatUtils::FORMAT_VERSION getFormat() const {
+        return mDictionaryFormat;
+    }
+
+    AK_FORCE_INLINE const BinaryDictionaryHeader *getHeader() const {
+        return &mDictionaryHeader;
+    }
+
+    AK_FORCE_INLINE bool isDynamicallyUpdatable() const {
+        // TODO: Support dynamic dictionary formats.
+        const bool isUpdatableDictionaryFormat = false;
+        return mIsUpdatable && isUpdatableDictionaryFormat;
+    }
+
+    AK_FORCE_INLINE const DictionaryStructurePolicy *getStructurePolicy() const {
+        return mStructurePolicy;
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryInfo);
+
+    const uint8_t *const mDictBuf;
+    const int mDictSize;
+    const int mMmapFd;
+    const int mDictBufOffset;
+    const bool mIsUpdatable;
+    const BinaryDictionaryFormatUtils::FORMAT_VERSION mDictionaryFormat;
+    const BinaryDictionaryHeader mDictionaryHeader;
+    const uint8_t *const mDictRoot;
+    const DictionaryStructurePolicy *const mStructurePolicy;
+
+    AK_FORCE_INLINE void logDictionaryInfo(JNIEnv *const env) const {
+        const int BUFFER_SIZE = 16;
+        int dictionaryIdCodePointBuffer[BUFFER_SIZE];
+        int versionStringCodePointBuffer[BUFFER_SIZE];
+        int dateStringCodePointBuffer[BUFFER_SIZE];
+        mDictionaryHeader.readHeaderValueOrQuestionMark("dictionary",
+                dictionaryIdCodePointBuffer, BUFFER_SIZE);
+        mDictionaryHeader.readHeaderValueOrQuestionMark("version",
+                versionStringCodePointBuffer, BUFFER_SIZE);
+        mDictionaryHeader.readHeaderValueOrQuestionMark("date",
+                dateStringCodePointBuffer, BUFFER_SIZE);
+
+        char dictionaryIdCharBuffer[BUFFER_SIZE];
+        char versionStringCharBuffer[BUFFER_SIZE];
+        char dateStringCharBuffer[BUFFER_SIZE];
+        intArrayToCharArray(dictionaryIdCodePointBuffer, BUFFER_SIZE,
+                dictionaryIdCharBuffer, BUFFER_SIZE);
+        intArrayToCharArray(versionStringCodePointBuffer, BUFFER_SIZE,
+                versionStringCharBuffer, BUFFER_SIZE);
+        intArrayToCharArray(dateStringCodePointBuffer, BUFFER_SIZE,
+                dateStringCharBuffer, BUFFER_SIZE);
+
+        LogUtils::logToJava(env,
+                "Dictionary info: dictionary = %s ; version = %s ; date = %s ; filesize = %i",
+                dictionaryIdCharBuffer, versionStringCharBuffer, dateStringCharBuffer, mDictSize);
+    }
+};
+}
+#endif /* LATINIME_BINARY_DICTIONARY_INFO_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
new file mode 100644
index 0000000..20b77b3
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+typedef BinaryDictionaryTerminalAttributesReadingUtils TaUtils;
+
+const TaUtils::TerminalAttributeFlags TaUtils::MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
+const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+// Flag for presence of more attributes
+const TaUtils::TerminalAttributeFlags TaUtils::FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
+// Mask for attribute probability, stored on 4 bits inside the flags byte.
+const TaUtils::TerminalAttributeFlags TaUtils::MASK_ATTRIBUTE_PROBABILITY = 0x0F;
+const int TaUtils::ATTRIBUTE_ADDRESS_SHIFT = 4;
+const int TaUtils::SHORTCUT_LIST_SIZE_FIELD_SIZE = 2;
+// The numeric value of the shortcut probability that means 'whitelist'.
+const int TaUtils::WHITELIST_SHORTCUT_PROBABILITY = 15;
+
+/* static */ int TaUtils::getBigramAddressAndForwardPointer(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const TerminalAttributeFlags flags,
+        int *const pos) {
+    int offset = 0;
+    const int origin = *pos;
+    switch (MASK_ATTRIBUTE_ADDRESS_TYPE & flags) {
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(
+                    binaryDictionaryInfo->getDictRoot(), pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(
+                    binaryDictionaryInfo->getDictRoot(), pos);
+            break;
+        case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(
+                    binaryDictionaryInfo->getDictRoot(), pos);
+            break;
+    }
+    if (isOffsetNegative(flags)) {
+        return origin - offset;
+    } else {
+        return origin + offset;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
new file mode 100644
index 0000000..375fc7d
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H
+#define LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+class BinaryDictionaryTerminalAttributesReadingUtils {
+ public:
+    typedef uint8_t TerminalAttributeFlags;
+    typedef TerminalAttributeFlags BigramFlags;
+    typedef TerminalAttributeFlags ShortcutFlags;
+
+    static AK_FORCE_INLINE TerminalAttributeFlags getFlagsAndForwardPointer(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
+        return ByteArrayUtils::readUint8AndAdvancePosition(
+                binaryDictionaryInfo->getDictRoot(), pos);
+    }
+
+    static AK_FORCE_INLINE int getProbabilityFromFlags(const TerminalAttributeFlags flags) {
+        return flags & MASK_ATTRIBUTE_PROBABILITY;
+    }
+
+    static AK_FORCE_INLINE bool hasNext(const TerminalAttributeFlags flags) {
+        return (flags & FLAG_ATTRIBUTE_HAS_NEXT) != 0;
+    }
+
+    // Bigrams reading methods
+    static AK_FORCE_INLINE void skipExistingBigrams(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
+        BigramFlags flags = getFlagsAndForwardPointer(binaryDictionaryInfo, pos);
+        while (hasNext(flags)) {
+            *pos += attributeAddressSize(flags);
+            flags = getFlagsAndForwardPointer(binaryDictionaryInfo, pos);
+        }
+        *pos += attributeAddressSize(flags);
+    }
+
+    static int getBigramAddressAndForwardPointer(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const BigramFlags flags,
+                    int *const pos);
+
+    // Shortcuts reading methods
+    // This method returns the size of the shortcut list region excluding the shortcut list size
+    // field at the beginning.
+    static AK_FORCE_INLINE int getShortcutListSizeAndForwardPointer(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
+        // readUint16andAdvancePosition() returns an offset *including* the uint16 field itself.
+        return ByteArrayUtils::readUint16AndAdvancePosition(
+                binaryDictionaryInfo->getDictRoot(), pos) - SHORTCUT_LIST_SIZE_FIELD_SIZE;
+    }
+
+    static AK_FORCE_INLINE void skipShortcuts(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, int *const pos) {
+        const int shortcutListSize = getShortcutListSizeAndForwardPointer(
+                binaryDictionaryInfo, pos);
+        *pos += shortcutListSize;
+    }
+
+    static AK_FORCE_INLINE bool isWhitelist(const ShortcutFlags flags) {
+        return getProbabilityFromFlags(flags) == WHITELIST_SHORTCUT_PROBABILITY;
+    }
+
+    static AK_FORCE_INLINE int readShortcutTarget(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int maxLength,
+            int *const outWord, int *const pos) {
+        return ByteArrayUtils::readStringAndAdvancePosition(
+                binaryDictionaryInfo->getDictRoot(), maxLength, outWord, pos);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryTerminalAttributesReadingUtils);
+
+    static const TerminalAttributeFlags MASK_ATTRIBUTE_ADDRESS_TYPE;
+    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+    static const TerminalAttributeFlags FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+    static const TerminalAttributeFlags FLAG_ATTRIBUTE_OFFSET_NEGATIVE;
+    static const TerminalAttributeFlags FLAG_ATTRIBUTE_HAS_NEXT;
+    static const TerminalAttributeFlags MASK_ATTRIBUTE_PROBABILITY;
+    static const int ATTRIBUTE_ADDRESS_SHIFT;
+    static const int SHORTCUT_LIST_SIZE_FIELD_SIZE;
+    static const int WHITELIST_SHORTCUT_PROBABILITY;
+
+    static AK_FORCE_INLINE bool isOffsetNegative(const TerminalAttributeFlags flags) {
+        return (flags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) != 0;
+    }
+
+    static AK_FORCE_INLINE int attributeAddressSize(const TerminalAttributeFlags flags) {
+        return (flags & MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
+        /* Note: this is a value-dependant optimization of what may probably be
+           more readably written this way:
+           switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
+           case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
+           case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
+           case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
+           default: return 0;
+           }
+        */
+    }
+};
+}
+#endif /* LATINIME_BINARY_DICTIONARY_TERMINAL_ATTRIBUTES_READING_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/bloom_filter.cpp b/native/jni/src/suggest/core/dictionary/bloom_filter.cpp
new file mode 100644
index 0000000..4ae474e
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/bloom_filter.cpp
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/bloom_filter.h"
+
+namespace latinime {
+
+// Must be smaller than BIGRAM_FILTER_BYTE_SIZE * 8, and preferably prime. 1021 is the largest
+// prime under 128 * 8.
+const int BloomFilter::BIGRAM_FILTER_MODULO = 1021;
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/bloom_filter.h b/native/jni/src/suggest/core/dictionary/bloom_filter.h
new file mode 100644
index 0000000..5205456
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/bloom_filter.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BLOOM_FILTER_H
+#define LATINIME_BLOOM_FILTER_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+// This bloom filter is used for optimizing bigram retrieval.
+// Execution times with previous word "this" are as follows:
+//  without bloom filter (use only hash_map):
+//   Total 147792.34 (sum of others 147771.57)
+//  with bloom filter:
+//   Total 145900.64 (sum of others 145874.30)
+//  always read binary dictionary:
+//   Total 148603.14 (sum of others 148579.90)
+class BloomFilter {
+ public:
+    BloomFilter() {
+        ASSERT(BIGRAM_FILTER_BYTE_SIZE * 8 >= BIGRAM_FILTER_MODULO);
+    }
+
+    // TODO: uint32_t position
+    AK_FORCE_INLINE void setInFilter(const int32_t position) {
+        const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
+        mFilter[bucket >> 3] |= static_cast<uint8_t>(1 << (bucket & 0x7));
+    }
+
+    // TODO: uint32_t position
+    AK_FORCE_INLINE bool isInFilter(const int32_t position) const {
+        const uint32_t bucket = static_cast<uint32_t>(position % BIGRAM_FILTER_MODULO);
+        return (mFilter[bucket >> 3] & static_cast<uint8_t>(1 << (bucket & 0x7))) != 0;
+    }
+
+ private:
+    // Size, in bytes, of the bloom filter index for bigrams
+    // 128 gives us 1024 buckets. The probability of false positive is (1 - e ** (-kn/m))**k,
+    // where k is the number of hash functions, n the number of bigrams, and m the number of
+    // bits we can test.
+    // At the moment 100 is the maximum number of bigrams for a word with the current
+    // dictionaries, so n = 100. 1024 buckets give us m = 1024.
+    // With 1 hash function, our false positive rate is about 9.3%, which should be enough for
+    // our uses since we are only using this to increase average performance. For the record,
+    // k = 2 gives 3.1% and k = 3 gives 1.6%. With k = 1, making m = 2048 gives 4.8%,
+    // and m = 4096 gives 2.4%.
+    // This is assigned here because it is used for array size.
+    static const int BIGRAM_FILTER_BYTE_SIZE = 128;
+    static const int BIGRAM_FILTER_MODULO;
+
+    uint8_t mFilter[BIGRAM_FILTER_BYTE_SIZE];
+};
+} // namespace latinime
+#endif // LATINIME_BLOOM_FILTER_H
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp b/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
new file mode 100644
index 0000000..68b1d5d
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/byte_array_utils.cpp
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+const uint8_t ByteArrayUtils::MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+const uint8_t ByteArrayUtils::CHARACTER_ARRAY_TERMINATOR = 0x1F;
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.h b/native/jni/src/suggest/core/dictionary/byte_array_utils.h
new file mode 100644
index 0000000..75ccfc7
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/byte_array_utils.h
@@ -0,0 +1,192 @@
+/*
+ * 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_BYTE_ARRAY_UTILS_H
+#define LATINIME_BYTE_ARRAY_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+/**
+ * Utility methods for reading byte arrays.
+ */
+class ByteArrayUtils {
+ public:
+    /**
+     * Integer
+     *
+     * Each method read a corresponding size integer in a big endian manner.
+     */
+    static AK_FORCE_INLINE uint32_t readUint32(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 24) ^ (buffer[pos + 1] << 16)
+                ^ (buffer[pos + 2] << 8) ^ buffer[pos + 3];
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint24(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 16) ^ (buffer[pos + 1] << 8) ^ buffer[pos + 2];
+    }
+
+    static AK_FORCE_INLINE uint16_t readUint16(const uint8_t *const buffer, const int pos) {
+        return (buffer[pos] << 8) ^ buffer[pos + 1];
+    }
+
+    static AK_FORCE_INLINE uint8_t readUint8(const uint8_t *const buffer, const int pos) {
+        return buffer[pos];
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint32AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint32_t value = readUint32(buffer, *pos);
+        *pos += 4;
+        return value;
+    }
+
+    static AK_FORCE_INLINE int readSint24AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t value = readUint8(buffer, *pos);
+        if (value < 0x80) {
+            return readUint24AndAdvancePosition(buffer, pos);
+        } else {
+            (*pos)++;
+            return -(((value & 0x7F) << 16) ^ readUint16AndAdvancePosition(buffer, pos));
+        }
+    }
+
+    static AK_FORCE_INLINE uint32_t readUint24AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint32_t value = readUint24(buffer, *pos);
+        *pos += 3;
+        return value;
+    }
+
+    static AK_FORCE_INLINE uint16_t readUint16AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint16_t value = readUint16(buffer, *pos);
+        *pos += 2;
+        return value;
+    }
+
+    static AK_FORCE_INLINE uint8_t readUint8AndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        return buffer[(*pos)++];
+    }
+
+    /**
+     * Code Point
+     *
+     * 1 byte = bbbbbbbb match
+     * case 000xxxxx: xxxxx << 16 + next byte << 8 + next byte
+     * else: if 00011111 (= 0x1F) : this is the terminator. This is a relevant choice because
+     *       unicode code points range from 0 to 0x10FFFF, so any 3-byte value starting with
+     *       00011111 would be outside unicode.
+     * else: iso-latin-1 code
+     * This allows for the whole unicode range to be encoded, including chars outside of
+     * the BMP. Also everything in the iso-latin-1 charset is only 1 byte, except control
+     * characters which should never happen anyway (and still work, but take 3 bytes).
+     */
+    static AK_FORCE_INLINE int readCodePoint(const uint8_t *const buffer, const int pos) {
+        int p = pos;
+        return readCodePointAndAdvancePosition(buffer, &p);
+    }
+
+    static AK_FORCE_INLINE int readCodePointAndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t firstByte = readUint8(buffer, *pos);
+        if (firstByte < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
+            if (firstByte == CHARACTER_ARRAY_TERMINATOR) {
+                *pos += 1;
+                return NOT_A_CODE_POINT;
+            } else {
+                return readUint24AndAdvancePosition(buffer, pos);
+            }
+        } else {
+            *pos += 1;
+            return firstByte;
+        }
+    }
+
+    /**
+     * String (array of code points)
+     *
+     * Reads code points until the terminator is found.
+     */
+    // Returns the length of the string.
+    static int readStringAndAdvancePosition(const uint8_t *const buffer,
+            const int maxLength, int *const outBuffer, int *const pos) {
+        int length = 0;
+        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
+            outBuffer[length++] = codePoint;
+            codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        }
+        return length;
+    }
+
+    // Advances the position and returns the length of the string.
+    static int advancePositionToBehindString(
+            const uint8_t *const buffer, const int maxLength, int *const pos) {
+        int length = 0;
+        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        while (NOT_A_CODE_POINT != codePoint && length < maxLength) {
+            codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        }
+        return length;
+    }
+
+    // Returns an integer less than, equal to, or greater than zero when string starting from pos
+    // in buffer is less than, match, or is greater than charArray.
+    static AK_FORCE_INLINE int compareStringInBufferWithCharArray(const uint8_t *const buffer,
+            const char *const charArray, const int maxLength, int *const pos) {
+        int index = 0;
+        int codePoint = readCodePointAndAdvancePosition(buffer, pos);
+        const uint8_t *const uint8CharArrayForComparison =
+                reinterpret_cast<const uint8_t *>(charArray);
+        while (NOT_A_CODE_POINT != codePoint
+                && '\0' != uint8CharArrayForComparison[index] && index < maxLength) {
+            if (codePoint != uint8CharArrayForComparison[index]) {
+                // Different character is found.
+                // Skip the rest of the string in the buffer.
+                advancePositionToBehindString(buffer, maxLength - index, pos);
+                return codePoint - uint8CharArrayForComparison[index];
+            }
+            // Advance
+            codePoint = readCodePointAndAdvancePosition(buffer, pos);
+            ++index;
+        }
+        if (NOT_A_CODE_POINT != codePoint && index < maxLength) {
+            // Skip the rest of the string in the buffer.
+            advancePositionToBehindString(buffer, maxLength - index, pos);
+        }
+        if (NOT_A_CODE_POINT == codePoint && '\0' == uint8CharArrayForComparison[index]) {
+            // When both of the last characters are terminals, we consider the string in the buffer
+            // matches the given char array
+            return 0;
+        } else {
+            return codePoint - uint8CharArrayForComparison[index];
+        }
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ByteArrayUtils);
+
+    static const uint8_t MINIMAL_ONE_BYTE_CHARACTER_VALUE;
+    static const uint8_t CHARACTER_ARRAY_TERMINATOR;
+};
+} // namespace latinime
+#endif /* LATINIME_BYTE_ARRAY_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
new file mode 100644
index 0000000..4f5d29f
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2009, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "LatinIME: dictionary.cpp"
+
+#include "suggest/core/dictionary/dictionary.h"
+
+#include <map> // TODO: remove
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/dictionary/bigram_dictionary.h"
+#include "suggest/core/session/dic_traverse_session.h"
+#include "suggest/core/suggest.h"
+#include "suggest/core/suggest_options.h"
+#include "suggest/policyimpl/gesture/gesture_suggest_policy_factory.h"
+#include "suggest/policyimpl/typing/typing_suggest_policy_factory.h"
+
+namespace latinime {
+
+Dictionary::Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd,
+        int dictBufOffset, bool isUpdatable)
+        : mBinaryDictionaryInfo(env, static_cast<const uint8_t *>(dict), dictSize, mmapFd,
+                dictBufOffset, isUpdatable),
+          mBigramDictionary(new BigramDictionary(&mBinaryDictionaryInfo)),
+          mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
+          mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) {
+}
+
+Dictionary::~Dictionary() {
+    delete mBigramDictionary;
+    delete mGestureSuggest;
+    delete mTypingSuggest;
+}
+
+int Dictionary::getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
+        int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
+        int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
+        const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+        int *spaceIndices, int *outputTypes) const {
+    int result = 0;
+    if (suggestOptions->isGesture()) {
+        DicTraverseSession::initSessionInstance(
+                traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
+        result = mGestureSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+                ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint, outWords,
+                frequencies, spaceIndices, outputTypes);
+        if (DEBUG_DICT) {
+            DUMP_RESULT(outWords, frequencies);
+        }
+        return result;
+    } else {
+        DicTraverseSession::initSessionInstance(
+                traverseSession, this, prevWordCodePoints, prevWordLength, suggestOptions);
+        result = mTypingSuggest->getSuggestions(proximityInfo, traverseSession, xcoordinates,
+                ycoordinates, times, pointerIds, inputCodePoints, inputSize, commitPoint,
+                outWords, frequencies, spaceIndices, outputTypes);
+        if (DEBUG_DICT) {
+            DUMP_RESULT(outWords, frequencies);
+        }
+        return result;
+    }
+}
+
+int Dictionary::getBigrams(const int *word, int length, int *inputCodePoints, int inputSize,
+        int *outWords, int *frequencies, int *outputTypes) const {
+    if (length <= 0) return 0;
+    return mBigramDictionary->getPredictions(word, length, inputCodePoints, inputSize, outWords,
+            frequencies, outputTypes);
+}
+
+int Dictionary::getProbability(const int *word, int length) const {
+    const DictionaryStructurePolicy *const structurePolicy =
+            mBinaryDictionaryInfo.getStructurePolicy();
+    int pos = structurePolicy->getTerminalNodePositionOfWord(&mBinaryDictionaryInfo, word, length,
+            false /* forceLowerCaseSearch */);
+    if (NOT_A_VALID_WORD_POS == pos) {
+        return NOT_A_PROBABILITY;
+    }
+    return structurePolicy->getUnigramProbability(&mBinaryDictionaryInfo, pos);
+}
+
+bool Dictionary::isValidBigram(const int *word0, int length0, const int *word1, int length1) const {
+    return mBigramDictionary->isValidBigram(word0, length0, word1, length1);
+}
+
+void Dictionary::addUnigramWord(const int *const word, const int length, const int probability) {
+    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: Dictionary::addUnigramWord() is called for non-updatable dictionary.");
+        return;
+    }
+    // TODO: Support dynamic update
+}
+
+void Dictionary::addBigramWords(const int *const word0, const int length0, const int *const word1,
+        const int length1, const int probability) {
+    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: Dictionary::addBigramWords() is called for non-updatable dictionary.");
+        return;
+    }
+    // TODO: Support dynamic update
+}
+
+void Dictionary::removeBigramWords(const int *const word0, const int length0,
+        const int *const word1, const int length1) {
+    if (!mBinaryDictionaryInfo.isDynamicallyUpdatable()) {
+        // This method should not be called for non-updatable dictionary.
+        AKLOGI("Warning: Dictionary::removeBigramWords() is called for non-updatable dictionary.");
+        return;
+    }
+    // TODO: Support dynamic update
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
new file mode 100644
index 0000000..1bf24a8
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DICTIONARY_H
+#define LATINIME_DICTIONARY_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "jni.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+
+namespace latinime {
+
+class BigramDictionary;
+class DicTraverseSession;
+class ProximityInfo;
+class SuggestInterface;
+class SuggestOptions;
+
+class Dictionary {
+ public:
+    // Taken from SuggestedWords.java
+    static const int KIND_MASK_KIND = 0xFF; // Mask to get only the kind
+    static const int KIND_TYPED = 0; // What user typed
+    static const int KIND_CORRECTION = 1; // Simple correction/suggestion
+    static const int KIND_COMPLETION = 2; // Completion (suggestion with appended chars)
+    static const int KIND_WHITELIST = 3; // Whitelisted word
+    static const int KIND_BLACKLIST = 4; // Blacklisted word
+    static const int KIND_HARDCODED = 5; // Hardcoded suggestion, e.g. punctuation
+    static const int KIND_APP_DEFINED = 6; // Suggested by the application
+    static const int KIND_SHORTCUT = 7; // A shortcut
+    static const int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input)
+    // KIND_RESUMED: A resumed suggestion (comes from a span, currently this type is used only
+    // in java for re-correction)
+    static const int KIND_RESUMED = 9;
+    static const int KIND_OOV_CORRECTION = 10; // Most probable string correction
+
+    static const int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags
+    static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
+    static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
+
+    Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd, int dictBufOffset,
+            bool isUpdatable);
+
+    int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
+            int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
+            int inputSize, int *prevWordCodePoints, int prevWordLength, int commitPoint,
+            const SuggestOptions *const suggestOptions, int *outWords, int *frequencies,
+            int *spaceIndices, int *outputTypes) const;
+
+    int getBigrams(const int *word, int length, int *inputCodePoints, int inputSize, int *outWords,
+            int *frequencies, int *outputTypes) const;
+
+    int getProbability(const int *word, int length) const;
+
+    bool isValidBigram(const int *word0, int length0, const int *word1, int length1) const;
+
+    void addUnigramWord(const int *const word, const int length, const int probability);
+
+    void addBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1, const int probability);
+
+    void removeBigramWords(const int *const word0, const int length0, const int *const word1,
+            const int length1);
+
+    const BinaryDictionaryInfo *getBinaryDictionaryInfo() const {
+        return &mBinaryDictionaryInfo;
+    }
+
+    virtual ~Dictionary();
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary);
+
+    const BinaryDictionaryInfo mBinaryDictionaryInfo;
+    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/suggest/core/dictionary/digraph_utils.cpp
similarity index 82%
rename from native/jni/src/digraph_utils.cpp
rename to native/jni/src/suggest/core/dictionary/digraph_utils.cpp
index 0834426..af378b1 100644
--- a/native/jni/src/digraph_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp
@@ -14,10 +14,13 @@
  * limitations under the License.
  */
 
-#include "char_utils.h"
-#include "binary_format.h"
+#include "suggest/core/dictionary/digraph_utils.h"
+
+#include <cstdlib>
+
 #include "defines.h"
-#include "digraph_utils.h"
+#include "suggest/core/dictionary/binary_dictionary_header.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -32,8 +35,8 @@
         { DIGRAPH_TYPE_GERMAN_UMLAUT, DIGRAPH_TYPE_FRENCH_LIGATURES };
 
 /* static */ bool DigraphUtils::hasDigraphForCodePoint(
-        const int dictFlags, const int compositeGlyphCodePoint) {
-    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(dictFlags);
+        const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint) {
+    const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(header);
     if (DigraphUtils::getDigraphForDigraphTypeAndCodePoint(digraphType, compositeGlyphCodePoint)) {
         return true;
     }
@@ -42,24 +45,16 @@
 
 // Returns the digraph type associated with the given dictionary.
 /* static */ DigraphUtils::DigraphType DigraphUtils::getDigraphTypeForDictionary(
-        const int dictFlags) {
-    if (BinaryFormat::REQUIRES_GERMAN_UMLAUT_PROCESSING & dictFlags) {
+        const BinaryDictionaryHeader *const header) {
+    if (header->requiresGermanUmlautProcessing()) {
         return DIGRAPH_TYPE_GERMAN_UMLAUT;
     }
-    if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & dictFlags) {
+    if (header->requiresFrenchLigatureProcessing()) {
         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,
@@ -121,9 +116,9 @@
 /* static */ const DigraphUtils::digraph_t *DigraphUtils::getDigraphForDigraphTypeAndCodePoint(
         const DigraphUtils::DigraphType digraphType, const int compositeGlyphCodePoint) {
     const DigraphUtils::digraph_t *digraphs = 0;
-    const int compositeGlyphLowerCodePoint = toLowerCase(compositeGlyphCodePoint);
+    const int compositeGlyphLowerCodePoint = CharUtils::toLowerCase(compositeGlyphCodePoint);
     const int digraphsSize =
-            DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(digraphType, &digraphs);
+            DigraphUtils::getAllDigraphsForDigraphTypeAndReturnSize(digraphType, &digraphs);
     for (int i = 0; i < digraphsSize; i++) {
         if (digraphs[i].compositeGlyph == compositeGlyphLowerCodePoint) {
             return &digraphs[i];
diff --git a/native/jni/src/digraph_utils.h b/native/jni/src/suggest/core/dictionary/digraph_utils.h
similarity index 80%
rename from native/jni/src/digraph_utils.h
rename to native/jni/src/suggest/core/dictionary/digraph_utils.h
index 9443522..9d74fe3 100644
--- a/native/jni/src/digraph_utils.h
+++ b/native/jni/src/suggest/core/dictionary/digraph_utils.h
@@ -17,8 +17,12 @@
 #ifndef DIGRAPH_UTILS_H
 #define DIGRAPH_UTILS_H
 
+#include "defines.h"
+
 namespace latinime {
 
+class BinaryDictionaryHeader;
+
 class DigraphUtils {
  public:
     typedef enum {
@@ -35,17 +39,14 @@
 
     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 bool hasDigraphForCodePoint(
+            const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint);
     static int getDigraphCodePointForIndex(const int compositeGlyphCodePoint,
             const DigraphCodePointIndex digraphCodePointIndex);
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DigraphUtils);
-    static DigraphType getDigraphTypeForDictionary(const int dictFlags);
+    static DigraphType getDigraphTypeForDictionary(const BinaryDictionaryHeader *const header);
     static int getAllDigraphsForDigraphTypeAndReturnSize(
             const DigraphType digraphType, const digraph_t **const digraphs);
     static const digraph_t *getDigraphForCodePoint(const int compositeGlyphCodePoint);
diff --git a/native/jni/src/suggest/core/dictionary/multi_bigram_map.cpp b/native/jni/src/suggest/core/dictionary/multi_bigram_map.cpp
new file mode 100644
index 0000000..b1d2f4b
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/core/dictionary/multi_bigram_map.h"
+
+#include <cstddef>
+
+namespace latinime {
+
+// Max number of bigram maps (previous word contexts) to be cached. Increasing this number
+// could improve bigram lookup speed for multi-word suggestions, but at the cost of more memory
+// usage. Also, there are diminishing returns since the most frequently used bigrams are
+// typically near the beginning of the input and are thus the first ones to be cached. Note
+// that these bigrams are reset for each new composing word.
+const size_t MultiBigramMap::MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP = 25;
+
+// Most common previous word contexts currently have 100 bigrams
+const int MultiBigramMap::BigramMap::DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP = 100;
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/core/dictionary/multi_bigram_map.h b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
new file mode 100644
index 0000000..d5eafe1
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/multi_bigram_map.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_MULTI_BIGRAM_MAP_H
+#define LATINIME_MULTI_BIGRAM_MAP_H
+
+#include <cstddef>
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_bigrams_iterator.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/bloom_filter.h"
+#include "suggest/core/dictionary/probability_utils.h"
+#include "utils/hash_map_compat.h"
+
+namespace latinime {
+
+// Class for caching bigram maps for multiple previous word contexts. This is useful since the
+// algorithm needs to look up the set of bigrams for every word pair that occurs in every
+// multi-word suggestion.
+class MultiBigramMap {
+ public:
+    MultiBigramMap() : mBigramMaps() {}
+    ~MultiBigramMap() {}
+
+    // Look up the bigram probability for the given word pair from the cached bigram maps.
+    // Also caches the bigrams if there is space remaining and they have not been cached already.
+    int getBigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int wordPosition, const int nextWordPosition, const int unigramProbability) {
+        hash_map_compat<int, BigramMap>::const_iterator mapPosition =
+                mBigramMaps.find(wordPosition);
+        if (mapPosition != mBigramMaps.end()) {
+            return mapPosition->second.getBigramProbability(nextWordPosition, unigramProbability);
+        }
+        if (mBigramMaps.size() < MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP) {
+            addBigramsForWordPosition(binaryDictionaryInfo, wordPosition);
+            return mBigramMaps[wordPosition].getBigramProbability(
+                    nextWordPosition, unigramProbability);
+        }
+        return readBigramProbabilityFromBinaryDictionary(binaryDictionaryInfo,
+                wordPosition, nextWordPosition, unigramProbability);
+    }
+
+    void clear() {
+        mBigramMaps.clear();
+    }
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(MultiBigramMap);
+
+    class BigramMap {
+     public:
+        BigramMap() : mBigramMap(DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP), mBloomFilter() {}
+        ~BigramMap() {}
+
+        void init(const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) {
+            const int bigramsListPos = binaryDictionaryInfo->getStructurePolicy()->
+                    getBigramsPositionOfNode(binaryDictionaryInfo, nodePos);
+            BinaryDictionaryBigramsIterator bigramsIt(binaryDictionaryInfo, bigramsListPos);
+            while (bigramsIt.hasNext()) {
+                bigramsIt.next();
+                mBigramMap[bigramsIt.getBigramPos()] = bigramsIt.getProbability();
+                mBloomFilter.setInFilter(bigramsIt.getBigramPos());
+            }
+        }
+
+        AK_FORCE_INLINE int getBigramProbability(
+                const int nextWordPosition, const int unigramProbability) const {
+            if (mBloomFilter.isInFilter(nextWordPosition)) {
+                const hash_map_compat<int, int>::const_iterator bigramProbabilityIt =
+                        mBigramMap.find(nextWordPosition);
+                if (bigramProbabilityIt != mBigramMap.end()) {
+                    const int bigramProbability = bigramProbabilityIt->second;
+                    return ProbabilityUtils::computeProbabilityForBigram(
+                            unigramProbability, bigramProbability);
+                }
+            }
+            return ProbabilityUtils::backoff(unigramProbability);
+        }
+
+     private:
+        // NOTE: The BigramMap class doesn't use DISALLOW_COPY_AND_ASSIGN() because its default
+        // copy constructor is needed for use in hash_map.
+        static const int DEFAULT_HASH_MAP_SIZE_FOR_EACH_BIGRAM_MAP;
+        hash_map_compat<int, int> mBigramMap;
+        BloomFilter mBloomFilter;
+    };
+
+    AK_FORCE_INLINE void addBigramsForWordPosition(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int position) {
+        mBigramMaps[position].init(binaryDictionaryInfo, position);
+    }
+
+    AK_FORCE_INLINE int readBigramProbabilityFromBinaryDictionary(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos,
+            const int nextWordPosition, const int unigramProbability) {
+        const int bigramsListPos = binaryDictionaryInfo->getStructurePolicy()->
+                getBigramsPositionOfNode(binaryDictionaryInfo, nodePos);
+        BinaryDictionaryBigramsIterator bigramsIt(binaryDictionaryInfo, bigramsListPos);
+        while (bigramsIt.hasNext()) {
+            bigramsIt.next();
+            if (bigramsIt.getBigramPos() == nextWordPosition) {
+                return ProbabilityUtils::computeProbabilityForBigram(
+                        unigramProbability, bigramsIt.getProbability());
+            }
+        }
+        return ProbabilityUtils::backoff(unigramProbability);
+    }
+
+    static const size_t MAX_CACHED_PREV_WORDS_IN_BIGRAM_MAP;
+    hash_map_compat<int, BigramMap> mBigramMaps;
+};
+} // namespace latinime
+#endif // LATINIME_MULTI_BIGRAM_MAP_H
diff --git a/native/jni/src/suggest/core/dictionary/probability_utils.h b/native/jni/src/suggest/core/dictionary/probability_utils.h
new file mode 100644
index 0000000..f450087
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/probability_utils.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_PROBABILITY_UTILS_H
+#define LATINIME_PROBABILITY_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class ProbabilityUtils {
+ public:
+    static AK_FORCE_INLINE int backoff(const int unigramProbability) {
+        return unigramProbability;
+        // For some reason, applying the backoff weight gives bad results in tests. To apply the
+        // backoff weight, we divide the probability by 2, which in our storing format means
+        // decreasing the score by 8.
+        // TODO: figure out what's wrong with this.
+        // return unigramProbability > 8 ?
+        //         unigramProbability - 8 : (0 == unigramProbability ? 0 : 8);
+    }
+
+    static AK_FORCE_INLINE int computeProbabilityForBigram(
+            const int unigramProbability, const int bigramProbability) {
+        // We divide the range [unigramProbability..255] in 16.5 steps - in other words, we want
+        // the unigram probability to be the median value of the 17th step from the top. A value of
+        // 0 for the bigram probability represents the middle of the 16th step from the top,
+        // while a value of 15 represents the middle of the top step.
+        // See makedict.BinaryDictInputOutput for details.
+        const float stepSize = static_cast<float>(MAX_PROBABILITY - unigramProbability)
+                / (1.5f + MAX_BIGRAM_ENCODED_PROBABILITY);
+        return unigramProbability
+                + static_cast<int>(static_cast<float>(bigramProbability + 1) * stepSize);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(ProbabilityUtils);
+};
+}
+#endif /* LATINIME_PROBABILITY_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/shortcut_utils.h b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
index c411408..3c21809 100644
--- a/native/jni/src/suggest/core/dictionary/shortcut_utils.h
+++ b/native/jni/src/suggest/core/dictionary/shortcut_utils.h
@@ -19,7 +19,7 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
-#include "terminal_attributes.h"
+#include "suggest/core/dictionary/terminal_attributes.h"
 
 namespace latinime {
 
@@ -29,15 +29,15 @@
             int outputWordIndex, const int finalScore, int *const outputCodePoints,
             int *const frequencies, int *const outputTypes, const bool sameAsTyped) {
         TerminalAttributes::ShortcutIterator iterator = terminalAttributes->getShortcutIterator();
+        int shortcutTarget[MAX_WORD_LENGTH];
         while (iterator.hasNextShortcutTarget() && outputWordIndex < MAX_RESULTS) {
-            int shortcutTarget[MAX_WORD_LENGTH];
-            int shortcutProbability;
-            const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
-                    MAX_WORD_LENGTH, shortcutTarget, &shortcutProbability);
+            bool isWhilelist;
+            int shortcutTargetStringLength;
+            iterator.nextShortcutTarget(MAX_WORD_LENGTH, shortcutTarget,
+                    &shortcutTargetStringLength, &isWhilelist);
             int shortcutScore;
             int kind;
-            if (shortcutProbability == BinaryFormat::WHITELIST_SHORTCUT_PROBABILITY
-                    && sameAsTyped) {
+            if (isWhilelist && sameAsTyped) {
                 shortcutScore = S_INT_MAX;
                 kind = Dictionary::KIND_WHITELIST;
             } else {
diff --git a/native/jni/src/suggest/core/dictionary/terminal_attributes.h b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
new file mode 100644
index 0000000..0da6504
--- /dev/null
+++ b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_TERMINAL_ATTRIBUTES_H
+#define LATINIME_TERMINAL_ATTRIBUTES_H
+
+#include <stdint.h>
+
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+
+namespace latinime {
+
+/**
+ * This class encapsulates information about a terminal that allows to
+ * retrieve local node attributes like the list of shortcuts without
+ * exposing the format structure to the client.
+ */
+class TerminalAttributes {
+ public:
+    class ShortcutIterator {
+     public:
+        ShortcutIterator(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+                const int shortcutPos, const bool hasShortcutList)
+                : mBinaryDictionaryInfo(binaryDictionaryInfo), mPos(shortcutPos),
+                  mHasNextShortcutTarget(hasShortcutList) {}
+
+        inline bool hasNextShortcutTarget() const {
+            return mHasNextShortcutTarget;
+        }
+
+        // Gets the shortcut target itself as an int string and put it to outTarget, put its length
+        // to outTargetLength, put whether it is whitelist to outIsWhitelist.
+        AK_FORCE_INLINE void nextShortcutTarget(
+                const int maxDepth, int *const outTarget, int *const outTargetLength,
+                bool *const outIsWhitelist) {
+            const BinaryDictionaryTerminalAttributesReadingUtils::ShortcutFlags flags =
+                    BinaryDictionaryTerminalAttributesReadingUtils::getFlagsAndForwardPointer(
+                            mBinaryDictionaryInfo, &mPos);
+            mHasNextShortcutTarget =
+                    BinaryDictionaryTerminalAttributesReadingUtils::hasNext(flags);
+            if (outIsWhitelist) {
+                *outIsWhitelist =
+                        BinaryDictionaryTerminalAttributesReadingUtils::isWhitelist(flags);
+            }
+            if (outTargetLength) {
+                *outTargetLength =
+                        BinaryDictionaryTerminalAttributesReadingUtils::readShortcutTarget(
+                                mBinaryDictionaryInfo, maxDepth, outTarget, &mPos);
+            }
+        }
+
+     private:
+        const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+        int mPos;
+        bool mHasNextShortcutTarget;
+    };
+
+    TerminalAttributes(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int shortcutPos)
+            : mBinaryDictionaryInfo(binaryDictionaryInfo), mShortcutListSizePos(shortcutPos) {}
+
+    inline ShortcutIterator getShortcutIterator() const {
+        int shortcutPos = mShortcutListSizePos;
+        const bool hasShortcutList = shortcutPos != NOT_A_DICT_POS;
+        if (hasShortcutList) {
+            BinaryDictionaryTerminalAttributesReadingUtils::getShortcutListSizeAndForwardPointer(
+                    mBinaryDictionaryInfo, &shortcutPos);
+        }
+        // shortcutPos is never used if hasShortcutList is false.
+        return ShortcutIterator(mBinaryDictionaryInfo, shortcutPos, hasShortcutList);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
+    const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
+    const int mShortcutListSizePos;
+};
+} // namespace latinime
+#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/additional_proximity_chars.cpp b/native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
similarity index 95%
rename from native/jni/src/additional_proximity_chars.cpp
rename to native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
index 661c50e..34b8b37 100644
--- a/native/jni/src/additional_proximity_chars.cpp
+++ b/native/jni/src/suggest/core/layout/additional_proximity_chars.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "additional_proximity_chars.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
 
 namespace latinime {
 // TODO: Stop using hardcoded additional proximity characters.
diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/suggest/core/layout/additional_proximity_chars.h
similarity index 100%
rename from native/jni/src/additional_proximity_chars.h
rename to native/jni/src/suggest/core/layout/additional_proximity_chars.h
diff --git a/native/jni/src/suggest/core/layout/geometry_utils.h b/native/jni/src/suggest/core/layout/geometry_utils.h
new file mode 100644
index 0000000..b667df6
--- /dev/null
+++ b/native/jni/src/suggest/core/layout/geometry_utils.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_GEOMETRY_UTILS_H
+#define LATINIME_GEOMETRY_UTILS_H
+
+#include <cmath>
+
+#include "defines.h"
+
+#define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \
+        ? (floorf((f) * 10000.0f) / 10000.0f) : (f)
+
+namespace latinime {
+
+class GeometryUtils {
+ public:
+    static inline float SQUARE_FLOAT(const float x) { return x * x; }
+
+    static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) {
+        const int dx = x1 - x2;
+        const int dy = y1 - y2;
+        if (dx == 0 && dy == 0) return 0.0f;
+        return atan2f(static_cast<float>(dy), static_cast<float>(dx));
+    }
+
+    static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) {
+        const float deltaA = fabsf(a1 - a2);
+        const float diff = ROUND_FLOAT_10000(deltaA);
+        if (diff > M_PI_F) {
+            const float normalizedDiff = 2.0f * M_PI_F - diff;
+            return ROUND_FLOAT_10000(normalizedDiff);
+        }
+        return diff;
+    }
+
+    static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2,
+            const int y2) {
+        return static_cast<int>(hypotf(static_cast<float>(x1 - x2), static_cast<float>(y1 - y2)));
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(GeometryUtils);
+};
+} // namespace latinime
+#endif // LATINIME_GEOMETRY_UTILS_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/suggest/core/layout/proximity_info.cpp
similarity index 61%
rename from native/jni/src/proximity_info.cpp
rename to native/jni/src/suggest/core/layout/proximity_info.cpp
index 88d670d..e64476d 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info.cpp
@@ -14,18 +14,19 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
+#include "suggest/core/layout/proximity_info.h"
+
 #include <cstring>
 #include <cmath>
 
-#define LOG_TAG "LatinIME: proximity_info.cpp"
-
-#include "additional_proximity_chars.h"
-#include "char_utils.h"
 #include "defines.h"
-#include "geometry_utils.h"
 #include "jni.h"
-#include "proximity_info.h"
-#include "proximity_info_params.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info_params.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -58,7 +59,7 @@
           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) /
+                  GeometryUtils::SQUARE_FLOAT(static_cast<float>(mostCommonKeyHeight) /
                           static_cast<float>(mostCommonKeyWidth))),
           CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
           CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight),
@@ -133,24 +134,13 @@
 }
 
 float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG(
-        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));
-    const float visualKeyCenterY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId));
-    float centerY;
-    if (correctTouchPosition) {
-        const float sweetSpotCenterY = static_cast<float>(getSweetSpotCenterYAt(keyId));
-        const float gapY = sweetSpotCenterY - visualKeyCenterY;
-        centerY = visualKeyCenterY + gapY * verticalScale;
-    } else {
-        centerY = visualKeyCenterY;
-    }
+        const int keyId, const int x, const int y, const bool isGeometric) const {
+    const float centerX = static_cast<float>(getKeyCenterXOfKeyIdG(keyId, x, isGeometric));
+    const float centerY = static_cast<float>(getKeyCenterYOfKeyIdG(keyId, y, isGeometric));
     const float touchX = static_cast<float>(x);
     const float touchY = static_cast<float>(y);
-    const float keyWidth = static_cast<float>(getMostCommonKeyWidth());
     return ProximityInfoUtils::getSquaredDistanceFloat(centerX, centerY, touchX, touchY)
-            / SQUARE_FLOAT(keyWidth);
+            / GeometryUtils::SQUARE_FLOAT(static_cast<float>(getMostCommonKeyWidth()));
 }
 
 int ProximityInfo::getCodePointOf(const int keyIndex) const {
@@ -164,44 +154,91 @@
     // TODO: Optimize
     for (int i = 0; i < KEY_COUNT; ++i) {
         const int code = mKeyCodePoints[i];
-        const int lowerCode = toLowerCase(code);
+        const int lowerCode = CharUtils::toLowerCase(code);
         mCenterXsG[i] = mKeyXCoordinates[i] + mKeyWidths[i] / 2;
         mCenterYsG[i] = mKeyYCoordinates[i] + mKeyHeights[i] / 2;
+        if (hasTouchPositionCorrectionData()) {
+            // Computes sweet spot center points for geometric input.
+            const float verticalScale = ProximityInfoParams::VERTICAL_SWEET_SPOT_SCALE_G;
+            const float sweetSpotCenterY = static_cast<float>(mSweetSpotCenterYs[i]);
+            const float gapY = sweetSpotCenterY - mCenterYsG[i];
+            mSweetSpotCenterYsG[i] = static_cast<int>(mCenterYsG[i] + gapY * verticalScale);
+        }
         mCodeToKeyMap[lowerCode] = i;
         mKeyIndexToCodePointG[i] = lowerCode;
     }
     for (int i = 0; i < KEY_COUNT; i++) {
         mKeyKeyDistancesG[i][i] = 0;
         for (int j = i + 1; j < KEY_COUNT; j++) {
-            mKeyKeyDistancesG[i][j] = getDistanceInt(
-                    mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
+            if (hasTouchPositionCorrectionData()) {
+                // Computes distances using sweet spots if they exist.
+                // We have two types of Y coordinate sweet spots, for geometric and for the others.
+                // The sweet spots for geometric input are used for calculating key-key distances
+                // here.
+                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
+                        mSweetSpotCenterXs[i], mSweetSpotCenterYsG[i],
+                        mSweetSpotCenterXs[j], mSweetSpotCenterYsG[j]);
+            } else {
+                mKeyKeyDistancesG[i][j] = GeometryUtils::getDistanceInt(
+                        mCenterXsG[i], mCenterYsG[i], mCenterXsG[j], mCenterYsG[j]);
+            }
             mKeyKeyDistancesG[j][i] = mKeyKeyDistancesG[i][j];
         }
     }
 }
 
-int ProximityInfo::getKeyCenterXOfCodePointG(int charCode) const {
-    return getKeyCenterXOfKeyIdG(
-            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
-}
-
-int ProximityInfo::getKeyCenterYOfCodePointG(int charCode) const {
-    return getKeyCenterYOfKeyIdG(
-            ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, charCode, &mCodeToKeyMap));
-}
-
-int ProximityInfo::getKeyCenterXOfKeyIdG(int keyId) const {
-    if (keyId >= 0) {
-        return mCenterXsG[keyId];
+// referencePointX is used only for keys wider than most common key width. When the referencePointX
+// is NOT_A_COORDINATE, this method calculates the return value without using the line segment.
+// isGeometric is currently not used because we don't have extra X coordinates sweet spots for
+// geometric input.
+int ProximityInfo::getKeyCenterXOfKeyIdG(
+        const int keyId, const int referencePointX, const bool isGeometric) const {
+    if (keyId < 0) {
+        return 0;
     }
-    return 0;
+    int centerX = (hasTouchPositionCorrectionData()) ? static_cast<int>(mSweetSpotCenterXs[keyId])
+            : mCenterXsG[keyId];
+    const int keyWidth = mKeyWidths[keyId];
+    if (referencePointX != NOT_A_COORDINATE
+            && keyWidth > getMostCommonKeyWidth()) {
+        // For keys wider than most common keys, we use a line segment instead of the center point;
+        // thus, centerX is adjusted depending on referencePointX.
+        const int keyWidthHalfDiff = (keyWidth - getMostCommonKeyWidth()) / 2;
+        if (referencePointX < centerX - keyWidthHalfDiff) {
+            centerX -= keyWidthHalfDiff;
+        } else if (referencePointX > centerX + keyWidthHalfDiff) {
+            centerX += keyWidthHalfDiff;
+        } else {
+            centerX = referencePointX;
+        }
+    }
+    return centerX;
 }
 
-int ProximityInfo::getKeyCenterYOfKeyIdG(int keyId) const {
-    if (keyId >= 0) {
-        return mCenterYsG[keyId];
+// When the referencePointY is NOT_A_COORDINATE, this method calculates the return value without
+// using the line segment.
+int ProximityInfo::getKeyCenterYOfKeyIdG(
+        const int keyId,  const int referencePointY, const bool isGeometric) const {
+    // TODO: Remove "isGeometric" and have separate "proximity_info"s for gesture and typing.
+    if (keyId < 0) {
+        return 0;
     }
-    return 0;
+    int centerY;
+    if (!hasTouchPositionCorrectionData()) {
+        centerY = mCenterYsG[keyId];
+    } else if (isGeometric) {
+        centerY = static_cast<int>(mSweetSpotCenterYsG[keyId]);
+    } else {
+        centerY = static_cast<int>(mSweetSpotCenterYs[keyId]);
+    }
+    if (referencePointY != NOT_A_COORDINATE &&
+            centerY + mKeyHeights[keyId] > KEYBOARD_HEIGHT && centerY < referencePointY) {
+        // When the distance between center point and bottom edge of the keyboard is shorter than
+        // the key height, we assume the key is located at the bottom row of the keyboard.
+        // The center point is extended to the bottom edge for such keys.
+        return referencePointY;
+    }
+    return centerY;
 }
 
 int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const {
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/suggest/core/layout/proximity_info.h
similarity index 90%
rename from native/jni/src/proximity_info.h
rename to native/jni/src/suggest/core/layout/proximity_info.h
index deb9ae0..f259490 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/suggest/core/layout/proximity_info.h
@@ -18,14 +18,12 @@
 #define LATINIME_PROXIMITY_INFO_H
 
 #include "defines.h"
-#include "hash_map_compat.h"
 #include "jni.h"
-#include "proximity_info_utils.h"
+#include "suggest/core/layout/proximity_info_utils.h"
+#include "utils/hash_map_compat.h"
 
 namespace latinime {
 
-class Correction;
-
 class ProximityInfo {
  public:
     ProximityInfo(JNIEnv *env, const jstring localeJStr,
@@ -39,9 +37,7 @@
     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 float verticalScale) const;
-    bool sameAsTyped(const unsigned short *word, int length) const;
+            const int keyId, const int x, const int y, const bool isGeometric) const;
     int getCodePointOf(const int keyIndex) const;
     bool hasSweetSpotData(const int keyIndex) const {
         // When there are no calibration data for a key,
@@ -68,10 +64,10 @@
     int getKeyboardHeight() const { return KEYBOARD_HEIGHT; }
     float getKeyboardHypotenuse() const { return KEYBOARD_HYPOTENUSE; }
 
-    int getKeyCenterXOfCodePointG(int charCode) const;
-    int getKeyCenterYOfCodePointG(int charCode) const;
-    int getKeyCenterXOfKeyIdG(int keyId) const;
-    int getKeyCenterYOfKeyIdG(int keyId) const;
+    int getKeyCenterXOfKeyIdG(
+            const int keyId, const int referencePointX, const bool isGeometric) const;
+    int getKeyCenterYOfKeyIdG(
+            const int keyId, const int referencePointY, const bool isGeometric) const;
     int getKeyKeyDistanceG(int keyId0, int keyId1) const;
 
     AK_FORCE_INLINE void initializeProximities(const int *const inputCodes,
@@ -95,8 +91,6 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
 
     void initializeG();
-    float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
-    bool hasInputCoordinates() const;
 
     const int GRID_WIDTH;
     const int GRID_HEIGHT;
@@ -120,6 +114,8 @@
     int mKeyCodePoints[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
+    // Sweet spots for geometric input. Note that we have extra sweet spots only for Y coordinates.
+    float mSweetSpotCenterYsG[MAX_KEY_COUNT_IN_A_KEYBOARD];
     float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD];
     hash_map_compat<int, int> mCodeToKeyMap;
 
diff --git a/native/jni/src/proximity_info_params.cpp b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
similarity index 98%
rename from native/jni/src/proximity_info_params.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_params.cpp
index 2675d9e..0e887f7 100644
--- a/native/jni/src/proximity_info_params.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_params.cpp
@@ -15,7 +15,7 @@
  */
 
 #include "defines.h"
-#include "proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
 const float ProximityInfoParams::NOT_A_DISTANCE_FLOAT = -1.0f;
diff --git a/native/jni/src/proximity_info_params.h b/native/jni/src/suggest/core/layout/proximity_info_params.h
similarity index 100%
rename from native/jni/src/proximity_info_params.h
rename to native/jni/src/suggest/core/layout/proximity_info_params.h
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
similarity index 86%
rename from native/jni/src/proximity_info_state.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_state.cpp
index cc5b736..7780efd 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp
@@ -14,17 +14,19 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "LatinIME: proximity_info_state.cpp"
+
+#include "suggest/core/layout/proximity_info_state.h"
+
 #include <cstring> // for memset() and memcpy()
 #include <sstream> // for debug prints
 #include <vector>
 
-#define LOG_TAG "LatinIME: proximity_info_state.cpp"
-
 #include "defines.h"
-#include "geometry_utils.h"
-#include "proximity_info.h"
-#include "proximity_info_state.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -95,15 +97,10 @@
                 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, verticalSweetSpotScale, inputSize, isGeometric, pointerId,
+                pointerIds, inputSize, isGeometric, pointerId,
                 pushTouchPointStartIndex, &mSampledInputXs, &mSampledInputYs, &mSampledTimes,
                 &mSampledLengthCache, &mSampledInputIndice);
     }
@@ -121,7 +118,7 @@
 
     if (mSampledInputSize > 0) {
         ProximityInfoStateUtils::initGeometricDistanceInfos(mProximityInfo, mSampledInputSize,
-                lastSavedInputSize, verticalSweetSpotScale, &mSampledInputXs, &mSampledInputYs,
+                lastSavedInputSize, isGeometric, &mSampledInputXs, &mSampledInputYs,
                 &mSampledNearKeySets, &mSampledNormalizedSquaredLengthCache);
         if (isGeometric) {
             // updates probabilities of skipping or mapping each key for all points.
@@ -154,11 +151,6 @@
     if (!isGeometric && pointerId == 0) {
         ProximityInfoStateUtils::initPrimaryInputWord(
                 inputSize, mInputProximities, mPrimaryInputWord);
-        if (mTouchPositionCorrectionEnabled) {
-            ProximityInfoStateUtils::initNormalizedSquaredDistances(
-                    mProximityInfo, inputSize, xCoordinates, yCoordinates, mInputProximities,
-                    &mSampledInputXs, &mSampledInputYs, mNormalizedSquaredDistances);
-        }
     }
     if (DEBUG_GEO_FULL) {
         AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize);
@@ -174,7 +166,7 @@
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
         return min(mSampledNormalizedSquaredLengthCache[index], mMaxPointToKeyLength);
     }
-    if (isIntentionalOmissionCodePoint(codePoint)) {
+    if (CharUtils::isIntentionalOmissionCodePoint(codePoint)) {
         return 0.0f;
     }
     // If the char is not a key on the keyboard then return the max length.
@@ -202,7 +194,7 @@
         const bool checkProximityChars, int *proximityIndex) const {
     const int *currentCodePoints = getProximityCodePointsAt(index);
     const int firstCodePoint = currentCodePoints[0];
-    const int baseLowerC = toBaseLowerCase(codePoint);
+    const int baseLowerC = CharUtils::toBaseLowerCase(codePoint);
 
     // The first char in the array is what user typed. If it matches right away, that means the
     // user typed that same char for this pos.
@@ -214,7 +206,7 @@
 
     // If the non-accented, lowercased version of that first character matches c, then we have a
     // non-accented version of the accented character the user typed. Treat it as a close char.
-    if (toBaseLowerCase(firstCodePoint) == baseLowerC) {
+    if (CharUtils::toBaseLowerCase(firstCodePoint) == baseLowerC) {
         return PROXIMITY_CHAR;
     }
 
@@ -256,8 +248,8 @@
     if (!isUsed()) {
         return UNRELATED_CHAR;
     }
-    const int lowerCodePoint = toLowerCase(codePoint);
-    const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint);
+    const int lowerCodePoint = CharUtils::toLowerCase(codePoint);
+    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
     for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) {
         if (mSampledSearchKeyVectors[index][i] == lowerCodePoint
                 || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) {
@@ -277,26 +269,6 @@
             &mSampledInputXs, &mSampledInputYs, index0, index1);
 }
 
-float ProximityInfoState::getLineToKeyDistance(
-        const int from, const int to, const int keyId, const bool extend) const {
-    if (from < 0 || from > mSampledInputSize - 1) {
-        return 0.0f;
-    }
-    if (to < 0 || to > mSampledInputSize - 1) {
-        return 0.0f;
-    }
-    const int x0 = mSampledInputXs[from];
-    const int y0 = mSampledInputYs[from];
-    const int x1 = mSampledInputXs[to];
-    const int y1 = mSampledInputYs[to];
-
-    const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId);
-    const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId);
-
-    return ProximityInfoUtils::pointToLineSegSquaredDistanceFloat(
-            keyX, keyY, x0, y0, x1, y1, extend);
-}
-
 float ProximityInfoState::getMostProbableString(int *const codePointBuf) const {
     memcpy(codePointBuf, mMostProbableString, sizeof(mMostProbableString));
     return mMostProbableStringProbability;
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h
similarity index 87%
rename from native/jni/src/proximity_info_state.h
rename to native/jni/src/suggest/core/layout/proximity_info_state.h
index bbe8af2..dbcd544 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state.h
@@ -20,11 +20,10 @@
 #include <cstring> // for memset()
 #include <vector>
 
-#include "char_utils.h"
 #include "defines.h"
-#include "hash_map_compat.h"
-#include "proximity_info_params.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/layout/proximity_info_params.h"
+#include "suggest/core/layout/proximity_info_state_utils.h"
+#include "utils/hash_map_compat.h"
 
 namespace latinime {
 
@@ -54,7 +53,6 @@
               mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false),
               mSampledInputSize(0), mMostProbableStringProbability(0.0f) {
         memset(mInputProximities, 0, sizeof(mInputProximities));
-        memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
         memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
         memset(mMostProbableString, 0, sizeof(mMostProbableString));
     }
@@ -92,7 +90,7 @@
         return false;
     }
 
-    inline bool existsAdjacentProximityChars(const int index) const {
+    AK_FORCE_INLINE bool existsAdjacentProximityChars(const int index) const {
         if (index < 0 || index >= mSampledInputSize) return false;
         const int currentCodePoint = getPrimaryCodePointAt(index);
         const int leftIndex = index - 1;
@@ -107,12 +105,6 @@
         return false;
     }
 
-    inline int getNormalizedSquaredDistance(
-            const int inputIndex, const int proximityIndex) const {
-        return mNormalizedSquaredDistances[
-                inputIndex * MAX_PROXIMITY_CHARS_SIZE + proximityIndex];
-    }
-
     inline const int *getPrimaryInputWord() const {
         return mPrimaryInputWord;
     }
@@ -191,24 +183,10 @@
 
     float getProbability(const int index, const int charCode) const;
 
-    float getLineToKeyDistance(
-            const int from, const int to, const int keyId, const bool extend) const;
-
     bool isKeyInSerchKeysAfterIndex(const int index, const int keyId) const;
 
  private:
     DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
-    /////////////////////////////////////////
-    // Defined in proximity_info_state.cpp //
-    /////////////////////////////////////////
-    float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const;
-
-    float calculateSquaredDistanceFromSweetSpotCenter(
-            const int keyIndex, const int inputIndex) const;
-
-    /////////////////////////////////////////
-    // Defined here                        //
-    /////////////////////////////////////////
 
     inline const int *getProximityCodePointsAt(const int index) const {
         return ProximityInfoStateUtils::getProximityCodePointsAt(mInputProximities, index);
@@ -250,7 +228,6 @@
     std::vector<std::vector<int> > mSampledSearchKeyVectors;
     bool mTouchPositionCorrectionEnabled;
     int mInputProximities[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH];
-    int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH];
     int mSampledInputSize;
     int mPrimaryInputWord[MAX_WORD_LENGTH];
     float mMostProbableStringProbability;
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
similarity index 90%
rename from native/jni/src/proximity_info_state_utils.cpp
rename to native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
index 359673c..904671f 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp
@@ -14,16 +14,17 @@
  * limitations under the License.
  */
 
+#include "suggest/core/layout/proximity_info_state_utils.h"
+
 #include <cmath>
 #include <cstring> // for memset()
 #include <sstream> // for debug prints
 #include <vector>
 
 #include "defines.h"
-#include "geometry_utils.h"
-#include "proximity_info.h"
-#include "proximity_info_params.h"
-#include "proximity_info_state_utils.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "suggest/core/layout/proximity_info.h"
+#include "suggest/core/layout/proximity_info_params.h"
 
 namespace latinime {
 
@@ -42,8 +43,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 float verticalSweetSpotScale, const int inputSize, const bool isGeometric,
-        const int pointerId, const int pushTouchPointStartIndex, std::vector<int> *sampledInputXs,
+        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) {
@@ -103,16 +104,16 @@
             const int time = times ? times[i] : -1;
 
             if (i > 1) {
-                const float prevAngle = getAngle(
+                const float prevAngle = GeometryUtils::getAngle(
                         inputXCoordinates[i - 2], inputYCoordinates[i - 2],
                         inputXCoordinates[i - 1], inputYCoordinates[i - 1]);
-                const float currentAngle = getAngle(
+                const float currentAngle = GeometryUtils::getAngle(
                         inputXCoordinates[i - 1], inputYCoordinates[i - 1], x, y);
-                sumAngle += getAngleDiff(prevAngle, currentAngle);
+                sumAngle += GeometryUtils::getAngleDiff(prevAngle, currentAngle);
             }
 
             if (pushTouchPoint(proximityInfo, maxPointToKeyLength, i, c, x, y, time,
-                    verticalSweetSpotScale, isGeometric /* doSampling */, i == lastInputIndex,
+                    isGeometric, isGeometric /* doSampling */, i == lastInputIndex,
                     sumAngle, currentNearKeysDistances, prevNearKeysDistances,
                     prevPrevNearKeysDistances, sampledInputXs, sampledInputYs, sampledInputTimes,
                     sampledLengthCache, sampledInputIndice)) {
@@ -157,7 +158,8 @@
     const float sweetSpotCenterY = proximityInfo->getSweetSpotCenterYAt(keyIndex);
     const float inputX = static_cast<float>((*sampledInputXs)[inputIndex]);
     const float inputY = static_cast<float>((*sampledInputYs)[inputIndex]);
-    return SQUARE_FLOAT(inputX - sweetSpotCenterX) + SQUARE_FLOAT(inputY - sweetSpotCenterY);
+    return GeometryUtils::SQUARE_FLOAT(inputX - sweetSpotCenterX)
+            + GeometryUtils::SQUARE_FLOAT(inputY - sweetSpotCenterY);
 }
 
 /* static */ float ProximityInfoStateUtils::calculateNormalizedSquaredDistance(
@@ -174,55 +176,14 @@
     }
     const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(proximityInfo,
             sampledInputXs, sampledInputYs, keyIndex, inputIndex);
-    const float squaredRadius = SQUARE_FLOAT(proximityInfo->getSweetSpotRadiiAt(keyIndex));
+    const float squaredRadius = GeometryUtils::SQUARE_FLOAT(
+            proximityInfo->getSweetSpotRadiiAt(keyIndex));
     return squaredDistance / squaredRadius;
 }
 
-/* static */ void ProximityInfoStateUtils::initNormalizedSquaredDistances(
-        const ProximityInfo *const proximityInfo, const int inputSize, const int *inputXCoordinates,
-        const int *inputYCoordinates, const int *const inputProximities,
-        const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs,
-        int *normalizedSquaredDistances) {
-    memset(normalizedSquaredDistances, NOT_A_DISTANCE,
-            sizeof(normalizedSquaredDistances[0]) * MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH);
-    const bool hasInputCoordinates = sampledInputXs->size() > 0 && sampledInputYs->size() > 0;
-    for (int i = 0; i < inputSize; ++i) {
-        const int *proximityCodePoints = getProximityCodePointsAt(inputProximities, i);
-        const int primaryKey = proximityCodePoints[0];
-        const int x = inputXCoordinates[i];
-        const int y = inputYCoordinates[i];
-        if (DEBUG_PROXIMITY_CHARS) {
-            int a = x + y + primaryKey;
-            a += 0;
-            AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y);
-        }
-        for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE && proximityCodePoints[j] > 0; ++j) {
-            const int currentCodePoint = proximityCodePoints[j];
-            const float squaredDistance =
-                    hasInputCoordinates ? calculateNormalizedSquaredDistance(
-                            proximityInfo, sampledInputXs, sampledInputYs,
-                            proximityInfo->getKeyIndexOf(currentCodePoint), i) :
-                            ProximityInfoParams::NOT_A_DISTANCE_FLOAT;
-            if (squaredDistance >= 0.0f) {
-                normalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        static_cast<int>(squaredDistance
-                                * ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
-            } else {
-                normalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        (j == 0) ? MATCH_CHAR_WITHOUT_DISTANCE_INFO :
-                                PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
-            }
-            if (DEBUG_PROXIMITY_CHARS) {
-                AKLOGI("--- Proximity (%d) = %c", j, currentCodePoint);
-            }
-        }
-    }
-
-}
-
 /* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos(
         const ProximityInfo *const proximityInfo, const int sampledInputSize,
-        const int lastSavedInputSize, const float verticalSweetSpotScale,
+        const int lastSavedInputSize, const bool isGeometric,
         const std::vector<int> *const sampledInputXs,
         const std::vector<int> *const sampledInputYs,
         std::vector<NearKeycodesSet> *sampledNearKeySets,
@@ -238,7 +199,7 @@
             const int y = (*sampledInputYs)[i];
             const float normalizedSquaredDistance =
                     proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(
-                            k, x, y, verticalSweetSpotScale);
+                            k, x, y, isGeometric);
             (*sampledNormalizedSquaredLengthCache)[index] = normalizedSquaredDistance;
             if (normalizedSquaredDistance
                     < ProximityInfoParams::NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) {
@@ -285,7 +246,7 @@
             if (i < sampledInputSize - 1 && j >= (*sampledInputIndice)[i + 1]) {
                 break;
             }
-            length += getDistanceInt(xCoordinates[j], yCoordinates[j],
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
@@ -296,7 +257,7 @@
                 break;
             }
             // TODO: use mSampledLengthCache instead?
-            length += getDistanceInt(xCoordinates[j], yCoordinates[j],
+            length += GeometryUtils::getDistanceInt(xCoordinates[j], yCoordinates[j],
                     xCoordinates[j + 1], yCoordinates[j + 1]);
             duration += times[j + 1] - times[j];
         }
@@ -349,21 +310,20 @@
     const int y1 = (*sampledInputYs)[index0];
     const int x2 = (*sampledInputXs)[index1];
     const int y2 = (*sampledInputYs)[index1];
-    return getAngle(x1, y1, x2, y2);
+    return GeometryUtils::getAngle(x1, y1, x2, y2);
 }
 
 // Calculating point to key distance for all near keys and returning the distance between
 // the given point and the nearest key position.
 /* static */ float ProximityInfoStateUtils::updateNearKeysDistances(
         const ProximityInfo *const proximityInfo, const float maxPointToKeyLength, const int x,
-        const int y, const float verticalSweetspotScale,
-        NearKeysDistanceMap *const currentNearKeysDistances) {
+        const int y, const bool isGeometric, NearKeysDistanceMap *const currentNearKeysDistances) {
     currentNearKeysDistances->clear();
     const int keyCount = proximityInfo->getKeyCount();
     float nearestKeyDistance = maxPointToKeyLength;
     for (int k = 0; k < keyCount; ++k) {
         const float dist = proximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y,
-                verticalSweetspotScale);
+                isGeometric);
         if (dist < ProximityInfoParams::NEAR_KEY_THRESHOLD_FOR_DISTANCE) {
             currentNearKeysDistances->insert(std::pair<int, float>(k, dist));
         }
@@ -411,9 +371,9 @@
     }
 
     const int baseSampleRate = mostCommonKeyWidth;
-    const int distPrev = getDistanceInt(sampledInputXs->back(), sampledInputYs->back(),
-            (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2])
-                    * ProximityInfoParams::DISTANCE_BASE_SCALE;
+    const int distPrev = GeometryUtils::getDistanceInt(sampledInputXs->back(),
+            sampledInputYs->back(), (*sampledInputXs)[size - 2],
+            (*sampledInputYs)[size - 2]) * ProximityInfoParams::DISTANCE_BASE_SCALE;
     float score = 0.0f;
 
     // Location
@@ -425,10 +385,11 @@
         score += ProximityInfoParams::LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
     }
     // Angle
-    const float angle1 = getAngle(x, y, sampledInputXs->back(), sampledInputYs->back());
-    const float angle2 = getAngle(sampledInputXs->back(), sampledInputYs->back(),
+    const float angle1 = GeometryUtils::getAngle(x, y, sampledInputXs->back(),
+            sampledInputYs->back());
+    const float angle2 = GeometryUtils::getAngle(sampledInputXs->back(), sampledInputYs->back(),
             (*sampledInputXs)[size - 2], (*sampledInputYs)[size - 2]);
-    const float angleDiff = getAngleDiff(angle1, angle2);
+    const float angleDiff = GeometryUtils::getAngleDiff(angle1, angle2);
 
     // Save corner
     if (distPrev > baseSampleRate * ProximityInfoParams::CORNER_CHECK_DISTANCE_THRESHOLD_SCALE
@@ -443,7 +404,7 @@
 // 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 float verticalSweetSpotScale, const bool doSampling,
+        const int time, const bool isGeometric, const bool doSampling,
         const bool isLastPoint, const float sumAngle,
         NearKeysDistanceMap *const currentNearKeysDistances,
         const NearKeysDistanceMap *const prevNearKeysDistances,
@@ -457,7 +418,7 @@
     bool popped = false;
     if (nodeCodePoint < 0 && doSampling) {
         const float nearest = updateNearKeysDistances(proximityInfo, maxPointToKeyLength, x, y,
-                verticalSweetSpotScale, currentNearKeysDistances);
+                isGeometric, currentNearKeysDistances);
         const float score = getPointScore(mostCommonKeyWidth, x, y, time, isLastPoint, nearest,
                 sumAngle, currentNearKeysDistances, prevNearKeysDistances,
                 prevPrevNearKeysDistances, sampledInputXs, sampledInputYs);
@@ -472,13 +433,13 @@
         }
         // Check if the last point should be skipped.
         if (isLastPoint && size > 0) {
-            if (getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
+            if (GeometryUtils::getDistanceInt(x, y, sampledInputXs->back(), sampledInputYs->back())
                     * ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE < mostCommonKeyWidth) {
                 // This point is not used because it's too close to the previous point.
                 if (DEBUG_GEO_FULL) {
                     AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
                            "width = %d", size, x, y, sampledInputXs->back(),
-                           sampledInputYs->back(), getDistanceInt(
+                           sampledInputYs->back(), GeometryUtils::getDistanceInt(
                                    x, y, sampledInputXs->back(), sampledInputYs->back()),
                            mostCommonKeyWidth
                                    / ProximityInfoParams::LAST_POINT_SKIP_DISTANCE_SCALE);
@@ -491,15 +452,15 @@
     if (nodeCodePoint >= 0 && (x < 0 || y < 0)) {
         const int keyId = proximityInfo->getKeyIndexOf(nodeCodePoint);
         if (keyId >= 0) {
-            x = proximityInfo->getKeyCenterXOfKeyIdG(keyId);
-            y = proximityInfo->getKeyCenterYOfKeyIdG(keyId);
+            x = proximityInfo->getKeyCenterXOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
+            y = proximityInfo->getKeyCenterYOfKeyIdG(keyId, NOT_AN_INDEX, isGeometric);
         }
     }
 
     // Pushing point information.
     if (size > 0) {
         sampledLengthCache->push_back(
-                sampledLengthCache->back() + getDistanceInt(
+                sampledLengthCache->back() + GeometryUtils::getDistanceInt(
                         x, y, sampledInputXs->back(), sampledInputYs->back()));
     } else {
         sampledLengthCache->push_back(0);
@@ -540,7 +501,8 @@
     while (start > 0 && tempBeelineDistance < lookupRadius) {
         tempTime += times[start] - times[start - 1];
         --start;
-        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[start], yCoordinates[start]);
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[start],
+                yCoordinates[start]);
     }
     // Exclusive unless this is an edge point
     if (start > 0 && start < actualInputIndex) {
@@ -553,7 +515,8 @@
     while (end < (inputSize - 1) && tempBeelineDistance < lookupRadius) {
         tempTime += times[end + 1] - times[end];
         ++end;
-        tempBeelineDistance = getDistanceInt(x0, y0, xCoordinates[end], yCoordinates[end]);
+        tempBeelineDistance = GeometryUtils::getDistanceInt(x0, y0, xCoordinates[end],
+                yCoordinates[end]);
     }
     // Exclusive unless this is an edge point
     if (end > actualInputIndex && end < (inputSize - 1)) {
@@ -571,7 +534,7 @@
     const int y2 = yCoordinates[start];
     const int x3 = xCoordinates[end];
     const int y3 = yCoordinates[end];
-    const int beelineDistance = getDistanceInt(x2, y2, x3, y3);
+    const int beelineDistance = GeometryUtils::getDistanceInt(x2, y2, x3, y3);
     int adjustedStartTime = times[start];
     if (start == 0 && actualInputIndex == 0 && inputSize > 1) {
         adjustedStartTime += ProximityInfoParams::FIRST_POINT_TIME_OFFSET_MILLIS;
@@ -613,7 +576,7 @@
     }
     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index - 1, index);
     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index, index + 1);
-    const float directionDiff = getAngleDiff(previousDirection, nextDirection);
+    const float directionDiff = GeometryUtils::getAngleDiff(previousDirection, nextDirection);
     return directionDiff;
 }
 
@@ -636,7 +599,7 @@
     }
     const float previousDirection = getDirection(sampledInputXs, sampledInputYs, index0, index1);
     const float nextDirection = getDirection(sampledInputXs, sampledInputYs, index1, index2);
-    return getAngleDiff(previousDirection, nextDirection);
+    return GeometryUtils::getAngleDiff(previousDirection, nextDirection);
 }
 
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
similarity index 97%
rename from native/jni/src/proximity_info_state_utils.h
rename to native/jni/src/suggest/core/layout/proximity_info_state_utils.h
index 1837c7a..6de9700 100644
--- a/native/jni/src/proximity_info_state_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.h
@@ -21,7 +21,7 @@
 #include <vector>
 
 #include "defines.h"
-#include "hash_map_compat.h"
+#include "utils/hash_map_compat.h"
 
 namespace latinime {
 class ProximityInfo;
@@ -38,8 +38,7 @@
     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 float verticalSweetSpotScale, const int inputSize,
+            const int *const times, const int *const pointerIds, const int inputSize,
             const bool isGeometric, const int pointerId, const int pushTouchPointStartIndex,
             std::vector<int> *sampledInputXs, std::vector<int> *sampledInputYs,
             std::vector<int> *sampledInputTimes, std::vector<int> *sampledLengthCache,
@@ -84,8 +83,7 @@
             const std::vector<float> *const sampledNormalizedSquaredLengthCache, const int keyCount,
             const int inputIndex, const int keyId);
     static void initGeometricDistanceInfos(const ProximityInfo *const proximityInfo,
-            const int sampledInputSize, const int lastSavedInputSize,
-            const float verticalSweetSpotScale,
+            const int sampledInputSize, const int lastSavedInputSize, const bool isGeometric,
             const std::vector<int> *const sampledInputXs,
             const std::vector<int> *const sampledInputYs,
             std::vector<NearKeycodesSet> *sampledNearKeySets,
@@ -120,7 +118,7 @@
 
     static float updateNearKeysDistances(const ProximityInfo *const proximityInfo,
             const float maxPointToKeyLength, const int x, const int y,
-            const float verticalSweetSpotScale,
+            const bool isGeometric,
             NearKeysDistanceMap *const currentNearKeysDistances);
     static bool isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
@@ -133,7 +131,7 @@
             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 float verticalSweetSpotScale,
+            int y, const int time, const bool isGeometric,
             const bool doSampling, const bool isLastPoint,
             const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances,
             const NearKeysDistanceMap *const prevNearKeysDistances,
diff --git a/native/jni/src/proximity_info_utils.h b/native/jni/src/suggest/core/layout/proximity_info_utils.h
similarity index 91%
rename from native/jni/src/proximity_info_utils.h
rename to native/jni/src/suggest/core/layout/proximity_info_utils.h
index 71c97e3..0e28560 100644
--- a/native/jni/src/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -19,11 +19,11 @@
 
 #include <cmath>
 
-#include "additional_proximity_chars.h"
-#include "char_utils.h"
 #include "defines.h"
-#include "geometry_utils.h"
-#include "hash_map_compat.h"
+#include "suggest/core/layout/additional_proximity_chars.h"
+#include "suggest/core/layout/geometry_utils.h"
+#include "utils/char_utils.h"
+#include "utils/hash_map_compat.h"
 
 namespace latinime {
 class ProximityInfoUtils {
@@ -37,7 +37,7 @@
         if (c == NOT_A_CODE_POINT) {
             return NOT_AN_INDEX;
         }
-        const int lowerCode = toLowerCase(c);
+        const int lowerCode = CharUtils::toLowerCase(c);
         hash_map_compat<int, int>::const_iterator mapPos = codeToKeyMap->find(lowerCode);
         if (mapPos != codeToKeyMap->end()) {
             return mapPos->second;
@@ -87,7 +87,7 @@
 
     static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2,
             const float y2) {
-        return SQUARE_FLOAT(x1 - x2) + SQUARE_FLOAT(y1 - y2);
+        return GeometryUtils::SQUARE_FLOAT(x1 - x2) + GeometryUtils::SQUARE_FLOAT(y1 - y2);
     }
 
     static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y,
@@ -98,7 +98,8 @@
         const float ray2y = y2 - y1;
 
         const float dotProduct = ray1x * ray2x + ray1y * ray2y;
-        const float lineLengthSqr = SQUARE_FLOAT(ray2x) + SQUARE_FLOAT(ray2y);
+        const float lineLengthSqr = GeometryUtils::SQUARE_FLOAT(ray2x)
+                + GeometryUtils::SQUARE_FLOAT(ray2y);
         const float projectionLengthSqr = dotProduct / lineLengthSqr;
 
         float projectionX;
@@ -116,17 +117,23 @@
         return getSquaredDistanceFloat(x, y, projectionX, projectionY);
     }
 
+     static AK_FORCE_INLINE bool isMatchOrProximityChar(const ProximityType type) {
+         return type == MATCH_CHAR || type == PROXIMITY_CHAR || type == ADDITIONAL_PROXIMITY_CHAR;
+     }
+
     // Normal distribution N(u, sigma^2).
     struct NormalDistribution {
      public:
         NormalDistribution(const float u, const float sigma)
                 : mU(u), mSigma(sigma),
-                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * SQUARE_FLOAT(sigma))),
-                  mPreComputedExponentPart(-1.0f / (2.0f * SQUARE_FLOAT(sigma))) {}
+                  mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F
+                          * GeometryUtils::SQUARE_FLOAT(sigma))),
+                  mPreComputedExponentPart(-1.0f / (2.0f * GeometryUtils::SQUARE_FLOAT(sigma))) {}
 
         float getProbabilityDensity(const float x) const {
             const float shiftedX = x - mU;
-            return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX));
+            return mPreComputedNonExpPart
+                    * expf(mPreComputedExponentPart * GeometryUtils::SQUARE_FLOAT(shiftedX));
         }
 
      private:
diff --git a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
new file mode 100644
index 0000000..9130e87
--- /dev/null
+++ b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
+#define LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
+
+#include "defines.h"
+#include "suggest/core/layout/proximity_info_params.h"
+
+namespace latinime {
+class TouchPositionCorrectionUtils {
+ public:
+    static float getSweetSpotFactor(const bool isTouchPositionCorrectionEnabled,
+            const float normalizedSquaredDistance) {
+        // Promote or demote the score according to the distance from the sweet spot
+        static const float A = 0.0f;
+        static const float B = 0.24f;
+        static const float C = 1.20f;
+        static const float R0 = 0.0f;
+        static const float R1 = 0.25f; // Sweet spot
+        static const float R2 = 1.0f;
+        const float x = normalizedSquaredDistance;
+        if (!isTouchPositionCorrectionEnabled) {
+            return min(C, x);
+        }
+
+        // factor is a piecewise linear function like:
+        // C        -------------.
+        //         /             .
+        // B      /              .
+        //      -/               .
+        // A _-^                 .
+        //                       .
+        //   R0 R1 R2            .
+
+        if (x < R0) {
+            return A;
+        } else if (x < R1) {
+            return (A * (R1 - x) + B * (x - R0)) / (R1 - R0);
+        } else if (x < R2) {
+            return (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
+        } else {
+            return C;
+        }
+    }
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(TouchPositionCorrectionUtils);
+};
+} // namespace latinime
+#endif // LATINIME_TOUCH_POSITION_CORRECTION_UTILS_H
diff --git a/native/jni/src/suggest/core/policy/dictionary_structure_policy.h b/native/jni/src/suggest/core/policy/dictionary_structure_policy.h
new file mode 100644
index 0000000..cc14c98
--- /dev/null
+++ b/native/jni/src/suggest/core/policy/dictionary_structure_policy.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_H
+#define LATINIME_DICTIONARY_STRUCTURE_POLICY_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class BinaryDictionaryInfo;
+class DicNode;
+class DicNodeVector;
+
+/*
+ * This class abstracts structure of dictionaries.
+ * Implement this policy to support additional dictionaries.
+ */
+class DictionaryStructurePolicy {
+ public:
+    // This provides a filtering method for filtering new node.
+    class NodeFilter {
+     public:
+        virtual bool isFilteredOut(const int codePoint) const = 0;
+
+     protected:
+        NodeFilter() {}
+        virtual ~NodeFilter() {}
+
+     private:
+        DISALLOW_COPY_AND_ASSIGN(NodeFilter);
+    };
+
+    virtual int getRootPosition() const = 0;
+
+    virtual void createAndGetAllChildNodes(const DicNode *const dicNode,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const = 0;
+
+    virtual int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const = 0;
+
+    virtual int getTerminalNodePositionOfWord(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const = 0;
+
+    virtual int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const = 0;
+
+    virtual int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const = 0;
+
+    virtual int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const = 0;
+
+ protected:
+    DictionaryStructurePolicy() {}
+    virtual ~DictionaryStructurePolicy() {}
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DictionaryStructurePolicy);
+};
+} // namespace latinime
+#endif /* LATINIME_DICTIONARY_STRUCTURE_POLICY_H */
diff --git a/native/jni/src/suggest/core/policy/traversal.h b/native/jni/src/suggest/core/policy/traversal.h
index c6f66f2..e935533 100644
--- a/native/jni/src/suggest/core/policy/traversal.h
+++ b/native/jni/src/suggest/core/policy/traversal.h
@@ -45,9 +45,9 @@
             const DicNode *const dicNode) const = 0;
     virtual bool needsToTraverseAllUserInput() const = 0;
     virtual float getMaxSpatialDistance() const = 0;
-    virtual bool allowPartialCommit() const = 0;
+    virtual bool autoCorrectsToMultiWordSuggestionIfTop() const = 0;
     virtual int getDefaultExpandDicNodeSize() const = 0;
-    virtual int getMaxCacheSize() const = 0;
+    virtual int getMaxCacheSize(const int inputSize) const = 0;
     virtual bool isPossibleOmissionChildNode(const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
     virtual bool isGoodToTraverseNextWord(const DicNode *const dicNode) const = 0;
diff --git a/native/jni/src/suggest/core/policy/weighting.cpp b/native/jni/src/suggest/core/policy/weighting.cpp
index d01531f..5872922 100644
--- a/native/jni/src/suggest/core/policy/weighting.cpp
+++ b/native/jni/src/suggest/core/policy/weighting.cpp
@@ -16,7 +16,6 @@
 
 #include "suggest/core/policy/weighting.h"
 
-#include "char_utils.h"
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_profiler.h"
@@ -51,6 +50,9 @@
     case CT_TERMINAL:
         PROF_TERMINAL(node->mProfiler);
         return;
+    case CT_TERMINAL_INSERTION:
+        PROF_TERMINAL_INSERTION(node->mProfiler);
+        return;
     case CT_NEW_WORD_SPACE_SUBSTITUTION:
         PROF_SPACE_SUBSTITUTION(node->mProfiler);
         return;
@@ -107,13 +109,15 @@
         // only used for typing
         return weighting->getSubstitutionCost();
     case CT_NEW_WORD_SPACE_OMITTION:
-        return weighting->getNewWordCost(traverseSession, dicNode);
+        return weighting->getNewWordSpatialCost(traverseSession, dicNode, inputStateG);
     case CT_MATCH:
         return weighting->getMatchedCost(traverseSession, dicNode, inputStateG);
     case CT_COMPLETION:
         return weighting->getCompletionCost(traverseSession, dicNode);
     case CT_TERMINAL:
         return weighting->getTerminalSpatialCost(traverseSession, dicNode);
+    case CT_TERMINAL_INSERTION:
+        return weighting->getTerminalInsertionCost(traverseSession, dicNode);
     case CT_NEW_WORD_SPACE_SUBSTITUTION:
         return weighting->getSpaceSubstitutionCost(traverseSession, dicNode);
     case CT_INSERTION:
@@ -135,7 +139,8 @@
     case CT_SUBSTITUTION:
         return 0.0f;
     case CT_NEW_WORD_SPACE_OMITTION:
-        return weighting->getNewWordBigramCost(traverseSession, parentDicNode, multiBigramMap);
+        return weighting->getNewWordBigramLanguageCost(
+                traverseSession, parentDicNode, multiBigramMap);
     case CT_MATCH:
         return 0.0f;
     case CT_COMPLETION:
@@ -143,11 +148,14 @@
     case CT_TERMINAL: {
         const float languageImprobability =
                 DicNodeUtils::getBigramNodeImprobability(
-                        traverseSession->getOffsetDict(), dicNode, multiBigramMap);
+                        traverseSession->getBinaryDictionaryInfo(), dicNode, multiBigramMap);
         return weighting->getTerminalLanguageCost(traverseSession, dicNode, languageImprobability);
     }
+    case CT_TERMINAL_INSERTION:
+        return 0.0f;
     case CT_NEW_WORD_SPACE_SUBSTITUTION:
-        return weighting->getNewWordBigramCost(traverseSession, parentDicNode, multiBigramMap);
+        return weighting->getNewWordBigramLanguageCost(
+                traverseSession, parentDicNode, multiBigramMap);
     case CT_INSERTION:
         return 0.0f;
     case CT_TRANSPOSITION:
@@ -162,9 +170,9 @@
         case CT_OMISSION:
             return 0;
         case CT_ADDITIONAL_PROXIMITY:
-            return 0;
+            return 0; /* 0 because CT_MATCH will be called */
         case CT_SUBSTITUTION:
-            return 0;
+            return 0; /* 0 because CT_MATCH will be called */
         case CT_NEW_WORD_SPACE_OMITTION:
             return 0;
         case CT_MATCH:
@@ -173,12 +181,14 @@
             return 1;
         case CT_TERMINAL:
             return 0;
+        case CT_TERMINAL_INSERTION:
+            return 1;
         case CT_NEW_WORD_SPACE_SUBSTITUTION:
             return 1;
         case CT_INSERTION:
-            return 2;
+            return 2; /* look ahead + skip the current char */
         case CT_TRANSPOSITION:
-            return 2;
+            return 2; /* look ahead + skip the current char */
         default:
             return 0;
     }
diff --git a/native/jni/src/suggest/core/policy/weighting.h b/native/jni/src/suggest/core/policy/weighting.h
index 0d2745b..2d49e98 100644
--- a/native/jni/src/suggest/core/policy/weighting.h
+++ b/native/jni/src/suggest/core/policy/weighting.h
@@ -56,10 +56,10 @@
             const DicTraverseSession *const traverseSession,
             const DicNode *const parentDicNode, const DicNode *const dicNode) const = 0;
 
-    virtual float getNewWordCost(const DicTraverseSession *const traverseSession,
-            const DicNode *const dicNode) const = 0;
+    virtual float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, DicNode_InputStateG *const inputStateG) const = 0;
 
-    virtual float getNewWordBigramCost(
+    virtual float getNewWordBigramLanguageCost(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
             MultiBigramMap *const multiBigramMap) const = 0;
 
@@ -67,6 +67,10 @@
             const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const = 0;
 
+    virtual float getTerminalInsertionCost(
+            const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const = 0;
+
     virtual float getTerminalLanguageCost(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode,
             float dicNodeLanguageImprobability) const = 0;
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
index 6408f01..7651b19 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp
@@ -16,68 +16,33 @@
 
 #include "suggest/core/session/dic_traverse_session.h"
 
-#include "binary_format.h"
 #include "defines.h"
-#include "dictionary.h"
-#include "dic_traverse_wrapper.h"
 #include "jni.h"
-#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dictionary/binary_dictionary_header.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/dictionary.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) {
+        int prevWordLength, const SuggestOptions *const suggestOptions) {
     mDictionary = dictionary;
-    mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier(mDictionary->getDict(),
-            mDictionary->getDictSize());
+    const BinaryDictionaryInfo *const binaryDictionaryInfo =
+            mDictionary->getBinaryDictionaryInfo();
+    mMultiWordCostMultiplier = binaryDictionaryInfo->getHeader()->getMultiWordCostMultiplier();
+    mSuggestOptions = suggestOptions;
     if (!prevWord) {
-        mPrevWordPos = NOT_VALID_WORD;
+        mPrevWordPos = NOT_A_VALID_WORD_POS;
         return;
     }
     // TODO: merge following similar calls to getTerminalPosition into one case-insensitive call.
-    mPrevWordPos = BinaryFormat::getTerminalPosition(dictionary->getOffsetDict(), prevWord,
-            prevWordLength, false /* forceLowerCaseSearch */);
-    if (mPrevWordPos == NOT_VALID_WORD) {
+    mPrevWordPos = binaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
+            binaryDictionaryInfo, prevWord, prevWordLength, false /* forceLowerCaseSearch */);
+    if (mPrevWordPos == NOT_A_VALID_WORD_POS) {
         // Check bigrams for lower-cased previous word if original was not found. Useful for
         // auto-capitalized words like "The [current_word]".
-        mPrevWordPos = BinaryFormat::getTerminalPosition(dictionary->getOffsetDict(), prevWord,
-                prevWordLength, true /* forceLowerCaseSearch */);
+        mPrevWordPos = binaryDictionaryInfo->getStructurePolicy()->getTerminalNodePositionOfWord(
+                binaryDictionaryInfo, prevWord, prevWordLength, true /* forceLowerCaseSearch */);
     }
 }
 
@@ -91,12 +56,8 @@
             maxSpatialDistance, maxPointerCount);
 }
 
-const uint8_t *DicTraverseSession::getOffsetDict() const {
-    return mDictionary->getOffsetDict();
-}
-
-int DicTraverseSession::getDictFlags() const {
-    return mDictionary->getDictFlags();
+const BinaryDictionaryInfo *DicTraverseSession::getBinaryDictionaryInfo() const {
+    return mDictionary->getBinaryDictionaryInfo();
 }
 
 void DicTraverseSession::resetCache(const int nextActiveCacheSize, const int maxWords) {
diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h
index d88be5b..de57e04 100644
--- a/native/jni/src/suggest/core/session/dic_traverse_session.h
+++ b/native/jni/src/suggest/core/session/dic_traverse_session.h
@@ -22,20 +22,41 @@
 
 #include "defines.h"
 #include "jni.h"
-#include "multi_bigram_map.h"
-#include "proximity_info_state.h"
 #include "suggest/core/dicnode/dic_nodes_cache.h"
+#include "suggest/core/dictionary/multi_bigram_map.h"
+#include "suggest/core/layout/proximity_info_state.h"
 
 namespace latinime {
 
+class BinaryDictionaryInfo;
 class Dictionary;
 class ProximityInfo;
+class SuggestOptions;
 
 class DicTraverseSession {
  public:
+
+    // A factory method for DicTraverseSession
+    static AK_FORCE_INLINE void *getSessionInstance(JNIEnv *env, jstring localeStr) {
+        return new DicTraverseSession(env, localeStr);
+    }
+
+    static AK_FORCE_INLINE void initSessionInstance(DicTraverseSession *traverseSession,
+            const Dictionary *const dictionary, const int *prevWord, const int prevWordLength,
+            const SuggestOptions *const suggestOptions) {
+        if (traverseSession) {
+            DicTraverseSession *tSession = static_cast<DicTraverseSession *>(traverseSession);
+            tSession->init(dictionary, prevWord, prevWordLength, suggestOptions);
+        }
+    }
+
+    static AK_FORCE_INLINE void releaseSessionInstance(DicTraverseSession *traverseSession) {
+        delete traverseSession;
+    }
+
     AK_FORCE_INLINE DicTraverseSession(JNIEnv *env, jstring localeStr)
-            : mPrevWordPos(NOT_VALID_WORD), mProximityInfo(0),
-              mDictionary(0), mDicNodesCache(), mMultiBigramMap(),
+            : mPrevWordPos(NOT_A_VALID_WORD_POS), mProximityInfo(0),
+              mDictionary(0), mSuggestOptions(0), mDicNodesCache(), mMultiBigramMap(),
               mInputSize(0), mPartiallyCommited(false), mMaxPointerCount(1),
               mMultiWordCostMultiplier(1.0f) {
         // NOTE: mProximityInfoStates is an array of instances.
@@ -45,7 +66,8 @@
     // Non virtual inline destructor -- never inherit this class
     AK_FORCE_INLINE ~DicTraverseSession() {}
 
-    void init(const Dictionary *dictionary, const int *prevWord, int prevWordLength);
+    void init(const Dictionary *dictionary, const int *prevWord, int prevWordLength,
+            const SuggestOptions *const suggestOptions);
     // TODO: Remove and merge into init
     void setupForGetSuggestions(const ProximityInfo *pInfo, const int *inputCodePoints,
             const int inputSize, const int *const inputXs, const int *const inputYs,
@@ -54,13 +76,13 @@
     void resetCache(const int nextActiveCacheSize, const int maxWords);
 
     // TODO: Remove
-    const uint8_t *getOffsetDict() const;
-    int getDictFlags() const;
+    const BinaryDictionaryInfo *getBinaryDictionaryInfo() const;
 
     //--------------------
     // getters and setters
     //--------------------
     const ProximityInfo *getProximityInfo() const { return mProximityInfo; }
+    const SuggestOptions *getSuggestOptions() const { return mSuggestOptions; }
     int getPrevWordPos() const { return mPrevWordPos; }
     // TODO: REMOVE
     void setPrevWordPos(int pos) { mPrevWordPos = pos; }
@@ -167,6 +189,7 @@
     int mPrevWordPos;
     const ProximityInfo *mProximityInfo;
     const Dictionary *mDictionary;
+    const SuggestOptions *mSuggestOptions;
 
     DicNodesCache mDicNodesCache;
     // Temporary cache for bigram frequencies
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index a187948..9376d7b 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -16,19 +16,19 @@
 
 #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/binary_dictionary_info.h"
+#include "suggest/core/dictionary/dictionary.h"
+#include "suggest/core/dictionary/digraph_utils.h"
 #include "suggest/core/dictionary/shortcut_utils.h"
+#include "suggest/core/dictionary/terminal_attributes.h"
+#include "suggest/core/layout/proximity_info.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 {
 
@@ -84,9 +84,9 @@
     if (!traverseSession->getProximityInfoState(0)->isUsed()) {
         return;
     }
-    if (TRAVERSAL->allowPartialCommit()) {
-        commitPoint = 0;
-    }
+
+    // Never auto partial commit for now.
+    commitPoint = 0;
 
     if (traverseSession->getInputSize() > MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE
             && traverseSession->isContinuousSuggestionPossible()) {
@@ -103,11 +103,12 @@
         }
     } else {
         // Restart recognition at the root.
-        traverseSession->resetCache(TRAVERSAL->getMaxCacheSize(), MAX_RESULTS);
+        traverseSession->resetCache(TRAVERSAL->getMaxCacheSize(traverseSession->getInputSize()),
+                MAX_RESULTS);
         // Create a new dic node here
         DicNode rootNode;
-        DicNodeUtils::initAsRoot(traverseSession->getDicRootPos(),
-                traverseSession->getOffsetDict(), traverseSession->getPrevWordPos(), &rootNode);
+        DicNodeUtils::initAsRoot(traverseSession->getBinaryDictionaryInfo(),
+                traverseSession->getPrevWordPos(), &rootNode);
         traverseSession->getDicTraverseCache()->copyPushActive(&rootNode);
     }
 }
@@ -148,6 +149,17 @@
             &doubleLetterTerminalIndex, &doubleLetterLevel);
 
     int maxScore = S_INT_MIN;
+    // Force autocorrection for obvious long multi-word suggestions when the top suggestion is
+    // a long multiple words suggestion.
+    // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
+    // traverseSession->isPartiallyCommited() always returns false because we never auto partial
+    // commit for now.
+    const bool forceCommitMultiWords = (terminalSize > 0) ?
+            TRAVERSAL->autoCorrectsToMultiWordSuggestionIfTop()
+                    && (traverseSession->isPartiallyCommited()
+                            || (traverseSession->getInputSize()
+                                    >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
+                                            && terminals[0].hasMultipleWords())) : false;
     // Output suggestion results here
     for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS;
             ++terminalIndex) {
@@ -159,8 +171,6 @@
                 terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel);
         const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
                 + doubleLetterCost;
-        const TerminalAttributes terminalAttributes(traverseSession->getOffsetDict(),
-                terminalDicNode->getFlags(), terminalDicNode->getAttributesPos());
         const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0;
         const bool isExactMatch = terminalDicNode->isExactMatch();
         const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
@@ -173,27 +183,21 @@
                 | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
 
         // Entries that are blacklisted or do not represent a word should not be output.
-        const bool isValidWord = !terminalAttributes.isBlacklistedOrNotAWord();
+        const bool isValidWord = !terminalDicNode->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()));
-
+                terminalDicNode->isExactMatch()
+                        || (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
+                                || (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);
-            }
+        // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
+        // Index for top typing suggestion should be 0.
+        if (isValidWord && outputWordIndex == 0) {
+            terminalDicNode->outputSpacePositionsResult(spaceIndices);
         }
 
         // Don't output invalid words. However, we still need to submit their shortcuts if any.
@@ -206,9 +210,18 @@
             ++outputWordIndex;
         }
 
-        const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
-        outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
-                finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+        if (!terminalDicNode->hasMultipleWords()) {
+            const BinaryDictionaryInfo *const binaryDictionaryInfo =
+                    traverseSession->getBinaryDictionaryInfo();
+            const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
+                    binaryDictionaryInfo->getStructurePolicy()->getShortcutPositionOfNode(
+                            binaryDictionaryInfo, terminalDicNode->getPos()));
+            // Shortcut is not supported for multiple words suggestions.
+            // TODO: Check shortcuts during traversal for multiple words suggestions.
+            const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
+            outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
+                    finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+        }
         DicNode::managedDelete(terminalDicNode);
     }
 
@@ -285,7 +298,7 @@
             }
 
             DicNodeUtils::getAllChildDicNodes(
-                    &dicNode, traverseSession->getOffsetDict(), &childDicNodes);
+                    &dicNode, traverseSession->getBinaryDictionaryInfo(), &childDicNodes);
 
             const int childDicNodesSize = childDicNodes.getSizeAndLock();
             for (int i = 0; i < childDicNodesSize; ++i) {
@@ -295,7 +308,8 @@
                     processDicNodeAsMatch(traverseSession, childDicNode);
                     continue;
                 }
-                if (DigraphUtils::hasDigraphForCodePoint(traverseSession->getDictFlags(),
+                if (DigraphUtils::hasDigraphForCodePoint(
+                        traverseSession->getBinaryDictionaryInfo()->getHeader(),
                         childDicNode->getNodeCodePoint())) {
                     correctionDicNode.initByCopy(childDicNode);
                     correctionDicNode.advanceDigraphIndex();
@@ -352,17 +366,17 @@
     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);
+    if (TRAVERSAL->needsToTraverseAllUserInput()
+            && dicNode->getInputIndex(0) < traverseSession->getInputSize()) {
+        Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL_INSERTION, traverseSession, 0,
+                &terminalDicNode, traverseSession->getMultiBigramMap());
+    }
     Weighting::addCostAndForwardInputIndex(WEIGHTING, CT_TERMINAL, traverseSession, 0,
             &terminalDicNode, traverseSession->getMultiBigramMap());
     traverseSession->getDicTraverseCache()->copyPushTerminal(&terminalDicNode);
@@ -432,7 +446,8 @@
 void Suggest::processDicNodeAsOmission(
         DicTraverseSession *traverseSession, DicNode *dicNode) const {
     DicNodeVector childDicNodes;
-    DicNodeUtils::getAllChildDicNodes(dicNode, traverseSession->getOffsetDict(), &childDicNodes);
+    DicNodeUtils::getAllChildDicNodes(
+            dicNode, traverseSession->getBinaryDictionaryInfo(), &childDicNodes);
 
     const int size = childDicNodes.getSizeAndLock();
     for (int i = 0; i < size; i++) {
@@ -457,7 +472,7 @@
         DicNode *dicNode) const {
     const int16_t pointIndex = dicNode->getInputIndex(0);
     DicNodeVector childDicNodes;
-    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(),
+    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getBinaryDictionaryInfo(),
             traverseSession->getProximityInfoState(0), pointIndex + 1, true, &childDicNodes);
     const int size = childDicNodes.getSizeAndLock();
     for (int i = 0; i < size; i++) {
@@ -475,14 +490,14 @@
         DicNode *dicNode) const {
     const int16_t pointIndex = dicNode->getInputIndex(0);
     DicNodeVector childDicNodes1;
-    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getOffsetDict(),
+    DicNodeUtils::getProximityChildDicNodes(dicNode, traverseSession->getBinaryDictionaryInfo(),
             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(),
+                    childDicNodes1[i], traverseSession->getBinaryDictionaryInfo(),
                     traverseSession->getProximityInfoState(0), pointIndex, false, &childDicNodes2);
             const int childSize2 = childDicNodes2.getSizeAndLock();
             for (int j = 0; j < childSize2; j++) {
@@ -522,12 +537,18 @@
 
     // Create a non-cached node here.
     DicNode newDicNode;
-    DicNodeUtils::initAsRootWithPreviousWord(traverseSession->getDicRootPos(),
-            traverseSession->getOffsetDict(), dicNode, &newDicNode);
+    DicNodeUtils::initAsRootWithPreviousWord(
+            traverseSession->getBinaryDictionaryInfo(), dicNode, &newDicNode);
     const CorrectionType correctionType = spaceSubstitution ?
             CT_NEW_WORD_SPACE_SUBSTITUTION : CT_NEW_WORD_SPACE_OMITTION;
     Weighting::addCostAndForwardInputIndex(WEIGHTING, correctionType, traverseSession, dicNode,
             &newDicNode, traverseSession->getMultiBigramMap());
-    traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+    if (newDicNode.getCompoundDistance() < static_cast<float>(MAX_VALUE_FOR_WEIGHTING)) {
+        // newDicNode is worth continuing to traverse.
+        // CAVEAT: This pruning is important for speed. Remove this when we can afford not to prune
+        // here because here is not the right place to do pruning. Pruning should take place only
+        // in DicNodePriorityQueue.
+        traverseSession->getDicTraverseCache()->copyPushNextActive(&newDicNode);
+    }
 }
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/suggest_options.h b/native/jni/src/suggest/core/suggest_options.h
new file mode 100644
index 0000000..1b21aaf
--- /dev/null
+++ b/native/jni/src/suggest/core/suggest_options.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_SUGGEST_OPTIONS_H
+#define LATINIME_SUGGEST_OPTIONS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class SuggestOptions{
+ public:
+    SuggestOptions(const int *const options, const int length)
+            : mOptions(options), mLength(length) {}
+
+    AK_FORCE_INLINE bool isGesture() const {
+        return getBoolOption(IS_GESTURE);
+    }
+
+    AK_FORCE_INLINE bool useFullEditDistance() const {
+        return getBoolOption(USE_FULL_EDIT_DISTANCE);
+    }
+
+    AK_FORCE_INLINE bool getAdditionalFeaturesBoolOption(const int key) const {
+        return getBoolOption(key + ADDITIONAL_FEATURES_OPTIONS);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(SuggestOptions);
+
+    // Need to update com.android.inputmethod.latin.NativeSuggestOptions when you add, remove or
+    // reorder options.
+    static const int IS_GESTURE = 0;
+    static const int USE_FULL_EDIT_DISTANCE = 1;
+    // Additional features options are stored after the other options and used as setting values of
+    // experimental features.
+    static const int ADDITIONAL_FEATURES_OPTIONS = 2;
+
+    const int *const mOptions;
+    const int mLength;
+
+    AK_FORCE_INLINE bool isValidKey(const int key) const {
+        return 0 <= key && key < mLength;
+    }
+
+    AK_FORCE_INLINE bool getBoolOption(const int key) const {
+        if (isValidKey(key)) {
+            return mOptions[key] != 0;
+        }
+        return false;
+    }
+
+    AK_FORCE_INLINE int getIntOption(const int key) const {
+        if (isValidKey(key)) {
+            return mOptions[key];
+        }
+        return 0;
+    }
+};
+} // namespace latinime
+#endif // LATINIME_SUGGEST_OPTIONS_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/binary_format.h b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
new file mode 100644
index 0000000..23f4c7f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/binary_format.h
@@ -0,0 +1,470 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_BINARY_FORMAT_H
+#define LATINIME_BINARY_FORMAT_H
+
+#include <stdint.h>
+
+#include "suggest/core/dictionary/probability_utils.h"
+#include "utils/char_utils.h"
+
+namespace latinime {
+
+class BinaryFormat {
+ public:
+    // Mask and flags for children address type selection.
+    static const int MASK_GROUP_ADDRESS_TYPE = 0xC0;
+
+    // Flag for single/multiple char group
+    static const int FLAG_HAS_MULTIPLE_CHARS = 0x20;
+
+    // Flag for terminal groups
+    static const int FLAG_IS_TERMINAL = 0x10;
+
+    // Flag for shortcut targets presence
+    static const int FLAG_HAS_SHORTCUT_TARGETS = 0x08;
+    // Flag for bigram presence
+    static const int FLAG_HAS_BIGRAMS = 0x04;
+    // Flag for non-words (typically, shortcut only entries)
+    static const int FLAG_IS_NOT_A_WORD = 0x02;
+    // Flag for blacklist
+    static const int FLAG_IS_BLACKLISTED = 0x01;
+
+    // Attribute (bigram/shortcut) related flags:
+    // Flag for presence of more attributes
+    static const int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
+    // Flag for sign of offset. If this flag is set, the offset value must be negated.
+    static const int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+
+    // Mask for attribute probability, stored on 4 bits inside the flags byte.
+    static const int MASK_ATTRIBUTE_PROBABILITY = 0x0F;
+
+    // Mask and flags for attribute address type selection.
+    static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
+
+    static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos);
+    static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos);
+    static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos);
+    static int readProbabilityWithoutMovingPointer(const uint8_t *const dict, const int pos);
+    static int skipOtherCharacters(const uint8_t *const dict, const int pos);
+    static int skipChildrenPosition(const uint8_t flags, const int pos);
+    static int skipProbability(const uint8_t flags, const int pos);
+    static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos);
+    static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags,
+            const int pos);
+    static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos);
+    static bool hasChildrenInFlags(const uint8_t flags);
+    static int getTerminalPosition(const uint8_t *const root, const int *const inWord,
+            const int length, const bool forceLowerCaseSearch);
+    static int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const uint8_t *const root, const int nodePos, const int maxCodePointCount,
+            int *const outCodePoints, int *const outUnigramProbability);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat);
+
+    static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
+    static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
+    static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
+    static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+    static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+
+    static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1;
+    static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+    static const int CHARACTER_ARRAY_TERMINATOR = 0x1F;
+    static const int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2;
+    static const int NO_FLAGS = 0;
+    static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos);
+    static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos);
+};
+
+AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict,
+        int *pos) {
+    const int msb = dict[(*pos)++];
+    if (msb < 0x80) return msb;
+    return ((msb & 0x7F) << 8) | dict[(*pos)++];
+}
+
+inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict, int *pos) {
+    return dict[(*pos)++];
+}
+
+AK_FORCE_INLINE int BinaryFormat::getCodePointAndForwardPointer(const uint8_t *const dict,
+        int *pos) {
+    const int origin = *pos;
+    const int codePoint = dict[origin];
+    if (codePoint < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
+        if (codePoint == CHARACTER_ARRAY_TERMINATOR) {
+            *pos = origin + 1;
+            return NOT_A_CODE_POINT;
+        } else {
+            *pos = origin + 3;
+            const int char_1 = codePoint << 16;
+            const int char_2 = char_1 + (dict[origin + 1] << 8);
+            return char_2 + dict[origin + 2];
+        }
+    } else {
+        *pos = origin + 1;
+        return codePoint;
+    }
+}
+
+inline int BinaryFormat::readProbabilityWithoutMovingPointer(const uint8_t *const dict,
+        const int pos) {
+    return dict[pos];
+}
+
+AK_FORCE_INLINE int BinaryFormat::skipOtherCharacters(const uint8_t *const dict, const int pos) {
+    int currentPos = pos;
+    int character = dict[currentPos++];
+    while (CHARACTER_ARRAY_TERMINATOR != character) {
+        if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) {
+            currentPos += MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE;
+        }
+        character = dict[currentPos++];
+    }
+    return currentPos;
+}
+
+static inline int attributeAddressSize(const uint8_t flags) {
+    static const int ATTRIBUTE_ADDRESS_SHIFT = 4;
+    return (flags & BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) >> ATTRIBUTE_ADDRESS_SHIFT;
+    /* Note: this is a value-dependant optimization of what may probably be
+       more readably written this way:
+       switch (flags * BinaryFormat::MASK_ATTRIBUTE_ADDRESS_TYPE) {
+       case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: return 1;
+       case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: return 2;
+       case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTE: return 3;
+       default: return 0;
+       }
+    */
+}
+
+static AK_FORCE_INLINE int skipExistingBigrams(const uint8_t *const dict, const int pos) {
+    int currentPos = pos;
+    uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
+    while (flags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT) {
+        currentPos += attributeAddressSize(flags);
+        flags = BinaryFormat::getFlagsAndForwardPointer(dict, &currentPos);
+    }
+    currentPos += attributeAddressSize(flags);
+    return currentPos;
+}
+
+static inline int childrenAddressSize(const uint8_t flags) {
+    static const int CHILDREN_ADDRESS_SHIFT = 6;
+    return (BinaryFormat::MASK_GROUP_ADDRESS_TYPE & flags) >> CHILDREN_ADDRESS_SHIFT;
+    /* See the note in attributeAddressSize. The same applies here */
+}
+
+static AK_FORCE_INLINE int shortcutByteSize(const uint8_t *const dict, const int pos) {
+    return (static_cast<int>(dict[pos] << 8)) + (dict[pos + 1]);
+}
+
+inline int BinaryFormat::skipChildrenPosition(const uint8_t flags, const int pos) {
+    return pos + childrenAddressSize(flags);
+}
+
+inline int BinaryFormat::skipProbability(const uint8_t flags, const int pos) {
+    return FLAG_IS_TERMINAL & flags ? pos + 1 : pos;
+}
+
+AK_FORCE_INLINE int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t flags,
+        const int pos) {
+    if (FLAG_HAS_SHORTCUT_TARGETS & flags) {
+        return pos + shortcutByteSize(dict, pos);
+    } else {
+        return pos;
+    }
+}
+
+AK_FORCE_INLINE int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t flags,
+        const int pos) {
+    if (FLAG_HAS_BIGRAMS & flags) {
+        return skipExistingBigrams(dict, pos);
+    } else {
+        return pos;
+    }
+}
+
+AK_FORCE_INLINE int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint8_t flags,
+        const int pos) {
+    // This function skips all attributes: shortcuts and bigrams.
+    int newPos = pos;
+    newPos = skipShortcuts(dict, flags, newPos);
+    newPos = skipBigrams(dict, flags, newPos);
+    return newPos;
+}
+
+AK_FORCE_INLINE int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict,
+        const uint8_t flags, const int pos) {
+    int currentPos = pos;
+    currentPos = skipChildrenPosition(flags, currentPos);
+    currentPos = skipAllAttributes(dict, flags, currentPos);
+    return currentPos;
+}
+
+AK_FORCE_INLINE int BinaryFormat::readChildrenPosition(const uint8_t *const dict,
+        const uint8_t flags, const int pos) {
+    int offset = 0;
+    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
+        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+            offset = dict[pos];
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+            offset = dict[pos] << 8;
+            offset += dict[pos + 1];
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+            offset = dict[pos] << 16;
+            offset += dict[pos + 1] << 8;
+            offset += dict[pos + 2];
+            break;
+        default:
+            // If we come here, it means we asked for the children of a word with
+            // no children.
+            return -1;
+    }
+    return pos + offset;
+}
+
+inline bool BinaryFormat::hasChildrenInFlags(const uint8_t flags) {
+    return (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags));
+}
+
+// This function gets the byte position of the last chargroup of the exact matching word in the
+// dictionary. If no match is found, it returns NOT_A_VALID_WORD_POS.
+AK_FORCE_INLINE int BinaryFormat::getTerminalPosition(const uint8_t *const root,
+        const int *const inWord, const int length, const bool forceLowerCaseSearch) {
+    int pos = 0;
+    int wordPos = 0;
+
+    while (true) {
+        // If we already traversed the tree further than the word is long, there means
+        // there was no match (or we would have found it).
+        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
+        int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos);
+        const int wChar = forceLowerCaseSearch
+                ? CharUtils::toLowerCase(inWord[wordPos]) : inWord[wordPos];
+        while (true) {
+            // If there are no more character groups in this node, it means we could not
+            // find a matching character for this depth, therefore there is no match.
+            if (0 >= charGroupCount) return NOT_A_VALID_WORD_POS;
+            const int charGroupPos = pos;
+            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
+            int character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
+            if (character == wChar) {
+                // This is the correct node. Only one character group may start with the same
+                // char within a node, so either we found our match in this node, or there is
+                // no match and we can return NOT_A_VALID_WORD_POS. So we will check all the
+                // characters in this character group indeed does match.
+                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                    character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
+                    while (NOT_A_CODE_POINT != character) {
+                        ++wordPos;
+                        // If we shoot the length of the word we search for, or if we find a single
+                        // character that does not match, as explained above, it means the word is
+                        // not in the dictionary (by virtue of this chargroup being the only one to
+                        // match the word on the first character, but not matching the whole word).
+                        if (wordPos >= length) return NOT_A_VALID_WORD_POS;
+                        if (inWord[wordPos] != character) return NOT_A_VALID_WORD_POS;
+                        character = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
+                    }
+                }
+                // If we come here we know that so far, we do match. Either we are on a terminal
+                // and we match the length, in which case we found it, or we traverse children.
+                // If we don't match the length AND don't have children, then a word in the
+                // dictionary fully matches a prefix of the searched word but not the full word.
+                ++wordPos;
+                if (FLAG_IS_TERMINAL & flags) {
+                    if (wordPos == length) {
+                        return charGroupPos;
+                    }
+                    pos = BinaryFormat::skipProbability(FLAG_IS_TERMINAL, pos);
+                }
+                if (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS == (MASK_GROUP_ADDRESS_TYPE & flags)) {
+                    return NOT_A_VALID_WORD_POS;
+                }
+                // We have children and we are still shorter than the word we are searching for, so
+                // we need to traverse children. Put the pointer on the children position, and
+                // break
+                pos = BinaryFormat::readChildrenPosition(root, flags, pos);
+                break;
+            } else {
+                // This chargroup does not match, so skip the remaining part and go to the next.
+                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                    pos = BinaryFormat::skipOtherCharacters(root, pos);
+                }
+                pos = BinaryFormat::skipProbability(flags, pos);
+                pos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
+            }
+            --charGroupCount;
+        }
+    }
+}
+
+// This function searches for a terminal in the dictionary by its address.
+// Due to the fact that words are ordered in the dictionary in a strict breadth-first order,
+// it is possible to check for this with advantageous complexity. For each node, we search
+// for groups with children and compare the children address with the address we look for.
+// When we shoot the address we look for, it means the word we look for is in the children
+// of the previous group. The only tricky part is the fact that if we arrive at the end of a
+// node with the last group's children address still less than what we are searching for, we
+// must descend the last group's children (for example, if the word we are searching for starts
+// with a z, it's the last group of the root node, so all children addresses will be smaller
+// than the address we look for, and we have to descend the z node).
+/* Parameters :
+ * root: the dictionary buffer
+ * address: the byte position of the last chargroup of the word we are searching for (this is
+ *   what is stored as the "bigram address" in each bigram)
+ * outword: an array to write the found word, with MAX_WORD_LENGTH size.
+ * outUnigramProbability: a pointer to an int to write the probability into.
+ * Return value : the length of the word, of 0 if the word was not found.
+ */
+AK_FORCE_INLINE int BinaryFormat::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const uint8_t *const root, const int nodePos, const int maxCodePointCount,
+        int *const outCodePoints, int *const outUnigramProbability) {
+    int pos = 0;
+    int wordPos = 0;
+
+    // One iteration of the outer loop iterates through nodes. As stated above, we will only
+    // traverse nodes that are actually a part of the terminal we are searching, so each time
+    // we enter this loop we are one depth level further than last time.
+    // The only reason we count nodes is because we want to reduce the probability of infinite
+    // looping in case there is a bug. Since we know there is an upper bound to the depth we are
+    // supposed to traverse, it does not hurt to count iterations.
+    for (int loopCount = maxCodePointCount; loopCount > 0; --loopCount) {
+        int lastCandidateGroupPos = 0;
+        // Let's loop through char groups in this node searching for either the terminal
+        // or one of its ascendants.
+        for (int charGroupCount = getGroupCountAndForwardPointer(root, &pos); charGroupCount > 0;
+                 --charGroupCount) {
+            const int startPos = pos;
+            const uint8_t flags = getFlagsAndForwardPointer(root, &pos);
+            const int character = getCodePointAndForwardPointer(root, &pos);
+            if (nodePos == startPos) {
+                // We found the address. Copy the rest of the word in the buffer and return
+                // the length.
+                outCodePoints[wordPos] = character;
+                if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                    int nextChar = getCodePointAndForwardPointer(root, &pos);
+                    // We count chars in order to avoid infinite loops if the file is broken or
+                    // if there is some other bug
+                    int charCount = maxCodePointCount;
+                    while (NOT_A_CODE_POINT != nextChar && --charCount > 0) {
+                        outCodePoints[++wordPos] = nextChar;
+                        nextChar = getCodePointAndForwardPointer(root, &pos);
+                    }
+                }
+                *outUnigramProbability = readProbabilityWithoutMovingPointer(root, pos);
+                return ++wordPos;
+            }
+            // We need to skip past this char group, so skip any remaining chars after the
+            // first and possibly the probability.
+            if (FLAG_HAS_MULTIPLE_CHARS & flags) {
+                pos = skipOtherCharacters(root, pos);
+            }
+            pos = skipProbability(flags, pos);
+
+            // The fact that this group has children is very important. Since we already know
+            // that this group does not match, if it has no children we know it is irrelevant
+            // to what we are searching for.
+            const bool hasChildren = (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS !=
+                    (MASK_GROUP_ADDRESS_TYPE & flags));
+            // We will write in `found' whether we have passed the children address we are
+            // searching for. For example if we search for "beer", the children of b are less
+            // than the address we are searching for and the children of c are greater. When we
+            // come here for c, we realize this is too big, and that we should descend b.
+            bool found;
+            if (hasChildren) {
+                // Here comes the tricky part. First, read the children position.
+                const int childrenPos = readChildrenPosition(root, flags, pos);
+                if (childrenPos > nodePos) {
+                    // If the children pos is greater than address, it means the previous chargroup,
+                    // which address is stored in lastCandidateGroupPos, was the right one.
+                    found = true;
+                } else if (1 >= charGroupCount) {
+                    // However if we are on the LAST group of this node, and we have NOT shot the
+                    // address we should descend THIS node. So we trick the lastCandidateGroupPos
+                    // so that we will descend this node, not the previous one.
+                    lastCandidateGroupPos = startPos;
+                    found = true;
+                } else {
+                    // Else, we should continue looking.
+                    found = false;
+                }
+            } else {
+                // Even if we don't have children here, we could still be on the last group of this
+                // node. If this is the case, we should descend the last group that had children,
+                // and their address is already in lastCandidateGroup.
+                found = (1 >= charGroupCount);
+            }
+
+            if (found) {
+                // Okay, we found the group we should descend. Its address is in
+                // the lastCandidateGroupPos variable, so we just re-read it.
+                if (0 != lastCandidateGroupPos) {
+                    const uint8_t lastFlags =
+                            getFlagsAndForwardPointer(root, &lastCandidateGroupPos);
+                    const int lastChar =
+                            getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
+                    // We copy all the characters in this group to the buffer
+                    outCodePoints[wordPos] = lastChar;
+                    if (FLAG_HAS_MULTIPLE_CHARS & lastFlags) {
+                        int nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
+                        int charCount = maxCodePointCount;
+                        while (-1 != nextChar && --charCount > 0) {
+                            outCodePoints[++wordPos] = nextChar;
+                            nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos);
+                        }
+                    }
+                    ++wordPos;
+                    // Now we only need to branch to the children address. Skip the probability if
+                    // it's there, read pos, and break to resume the search at pos.
+                    lastCandidateGroupPos = skipProbability(lastFlags, lastCandidateGroupPos);
+                    pos = readChildrenPosition(root, lastFlags, lastCandidateGroupPos);
+                    break;
+                } else {
+                    // Here is a little tricky part: we come here if we found out that all children
+                    // addresses in this group are bigger than the address we are searching for.
+                    // Should we conclude the word is not in the dictionary? No! It could still be
+                    // one of the remaining chargroups in this node, so we have to keep looking in
+                    // this node until we find it (or we realize it's not there either, in which
+                    // case it's actually not in the dictionary). Pass the end of this group, ready
+                    // to start the next one.
+                    pos = skipChildrenPosAndAttributes(root, flags, pos);
+                }
+            } else {
+                // If we did not find it, we should record the last children address for the next
+                // iteration.
+                if (hasChildren) lastCandidateGroupPos = startPos;
+                // Now skip the end of this group (children pos and the attributes if any) so that
+                // our pos is after the end of this char group, at the start of the next one.
+                pos = skipChildrenPosAndAttributes(root, flags, pos);
+            }
+
+        }
+    }
+    // If we have looked through all the chargroups and found no match, the address is
+    // not the address of a terminal in this dictionary.
+    return 0;
+}
+
+} // namespace latinime
+#endif // LATINIME_BINARY_FORMAT_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
new file mode 100644
index 0000000..c0df89f
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
+#define LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
+
+#include "defines.h"
+#include "suggest/core/dictionary/binary_dictionary_format_utils.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+
+namespace latinime {
+
+class DictionaryStructurePolicy;
+
+class DictionaryStructurePolicyFactory {
+ public:
+    static const DictionaryStructurePolicy *getDictionaryStructurePolicy(
+            const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
+        switch (dictionaryFormat) {
+            case BinaryDictionaryFormatUtils::VERSION_2:
+                return PatriciaTriePolicy::getInstance();
+            case BinaryDictionaryFormatUtils::VERSION_3:
+                return DynamicPatriciaTriePolicy::getInstance();
+            default:
+                ASSERT(false);
+                return 0;
+        }
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DictionaryStructurePolicyFactory);
+};
+} // namespace latinime
+#endif // LATINIME_DICTIONARY_STRUCTURE_POLICY_FACTORY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
new file mode 100644
index 0000000..c7314ec
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+
+namespace latinime {
+
+const DynamicPatriciaTriePolicy DynamicPatriciaTriePolicy::sInstance;
+
+void DynamicPatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const {
+    // TODO: Implement.
+}
+
+int DynamicPatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        int *const outUnigramProbability) const {
+    // TODO: Implement.
+    return 0;
+}
+
+int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) const {
+    // TODO: Implement.
+    return NOT_A_DICT_POS;
+}
+
+int DynamicPatriciaTriePolicy::getUnigramProbability(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) const {
+    // TODO: Implement.
+    return NOT_A_PROBABILITY;
+}
+
+int DynamicPatriciaTriePolicy::getShortcutPositionOfNode(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos) const {
+    // TODO: Implement.
+    return NOT_A_DICT_POS;
+}
+
+int DynamicPatriciaTriePolicy::getBigramsPositionOfNode(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos) const {
+    // TODO: Implement.
+    return NOT_A_DICT_POS;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
new file mode 100644
index 0000000..39dfb86
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_policy.h"
+
+namespace latinime {
+
+class BinaryDictionaryInfo;
+class DicNode;
+class DicNodeVector;
+
+class DynamicPatriciaTriePolicy : public DictionaryStructurePolicy {
+ public:
+    static AK_FORCE_INLINE const DynamicPatriciaTriePolicy *getInstance() {
+        return &sInstance;
+    }
+
+    AK_FORCE_INLINE int getRootPosition() const {
+        return 0;
+    }
+
+    void createAndGetAllChildNodes(const DicNode *const dicNode,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+
+    int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int terminalNodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const;
+
+    int getTerminalNodePositionOfWord(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const;
+
+    int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+    int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+    int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(DynamicPatriciaTriePolicy);
+    static const DynamicPatriciaTriePolicy sInstance;
+
+    DynamicPatriciaTriePolicy() {}
+    ~DynamicPatriciaTriePolicy() {}
+};
+} // namespace latinime
+#endif // LATINIME_DYNAMIC_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..0de6341
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.cpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+typedef DynamicPatriciaTrieReadingUtils DptReadingUtils;
+
+const DptReadingUtils::NodeFlags DptReadingUtils::MASK_MOVED = 0xC0;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_NOT_MOVED = 0xC0;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_MOVED = 0x40;
+const DptReadingUtils::NodeFlags DptReadingUtils::FLAG_IS_DELETED = 0x80;
+
+/* static */ int DptReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
+    if ((flags & MASK_MOVED) == FLAG_IS_NOT_MOVED) {
+        const int base = *pos;
+        return base + ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    } else {
+        return NOT_A_DICT_POS;
+    }
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
new file mode 100644
index 0000000..f44c265
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_reading_utils.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H
+#define LATINIME_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+class DynamicPatriciaTrieReadingUtils {
+ public:
+    typedef uint8_t NodeFlags;
+
+    static AK_FORCE_INLINE int getForwardLinkPosition(const uint8_t *const buffer, const int pos) {
+        int linkAddressPos = pos;
+        return ByteArrayUtils::readSint24AndAdvancePosition(buffer, &linkAddressPos);
+    }
+
+    static AK_FORCE_INLINE bool isValidForwardLinkPosition(const int forwardLinkAddress) {
+        return forwardLinkAddress != 0;
+    }
+
+    static AK_FORCE_INLINE int getParentPosAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        const int base = *pos;
+        return base + ByteArrayUtils::readSint24AndAdvancePosition(buffer, pos);
+    }
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, int *const pos);
+
+    /**
+     * Node Flags
+     */
+    static AK_FORCE_INLINE bool isMoved(const NodeFlags flags) {
+        return FLAG_IS_MOVED == (MASK_MOVED & flags);
+    }
+
+    static AK_FORCE_INLINE bool isDeleted(const NodeFlags flags) {
+        return FLAG_IS_DELETED == (MASK_MOVED & flags);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DynamicPatriciaTrieReadingUtils);
+
+    static const NodeFlags MASK_MOVED;
+    static const NodeFlags FLAG_IS_NOT_MOVED;
+    static const NodeFlags FLAG_IS_MOVED;
+    static const NodeFlags FLAG_IS_DELETED;
+};
+} // namespace latinime
+#endif /* LATINIME_DYNAMIC_PATRICIA_TRIE_READING_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
new file mode 100644
index 0000000..097f7c8
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.cpp
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+#include "suggest/core/dicnode/dic_node_vector.h"
+#include "suggest/core/dictionary/binary_dictionary_info.h"
+#include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
+#include "suggest/policyimpl/dictionary/binary_format.h"
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+namespace latinime {
+
+const PatriciaTriePolicy PatriciaTriePolicy::sInstance;
+
+void PatriciaTriePolicy::createAndGetAllChildNodes(const DicNode *const dicNode,
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const {
+    if (!dicNode->hasChildren()) {
+        return;
+    }
+    int nextPos = dicNode->getChildrenPos();
+    const int childCount = PatriciaTrieReadingUtils::getGroupCountAndAdvancePosition(
+            binaryDictionaryInfo->getDictRoot(), &nextPos);
+    for (int i = 0; i < childCount; i++) {
+        nextPos = createAndGetLeavingChildNode(dicNode, nextPos, binaryDictionaryInfo,
+                nodeFilter, childDicNodes);
+    }
+}
+
+int PatriciaTriePolicy::getCodePointsAndProbabilityAndReturnCodePointCount(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos, const int maxCodePointCount, int *const outCodePoints,
+        int *const outUnigramProbability) const {
+    return BinaryFormat::getCodePointsAndProbabilityAndReturnCodePointCount(
+            binaryDictionaryInfo->getDictRoot(), nodePos,
+            maxCodePointCount, outCodePoints, outUnigramProbability);
+}
+
+int PatriciaTriePolicy::getTerminalNodePositionOfWord(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+        const int length, const bool forceLowerCaseSearch) const {
+    return BinaryFormat::getTerminalPosition(binaryDictionaryInfo->getDictRoot(), inWord,
+            length, forceLowerCaseSearch);
+}
+
+int PatriciaTriePolicy::getUnigramProbability(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo, const int nodePos) const {
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_PROBABILITY;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::isTerminal(flags)) {
+        return NOT_A_PROBABILITY;
+    }
+    if (PatriciaTrieReadingUtils::isNotAWord(flags)
+            || PatriciaTrieReadingUtils::isBlacklisted(flags)) {
+        // If this is not a word, or if it's a blacklisted entry, it should behave as
+        // having no probability outside of the suggestion process (where it should be used
+        // for shortcuts).
+        return NOT_A_PROBABILITY;
+    }
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    return PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+}
+
+int PatriciaTriePolicy::getShortcutPositionOfNode(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos) const {
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        return NOT_A_DICT_POS;
+    }
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+    }
+    return pos;
+}
+
+int PatriciaTriePolicy::getBigramsPositionOfNode(
+        const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const int nodePos) const {
+    if (nodePos == NOT_A_VALID_WORD_POS) {
+        return NOT_A_DICT_POS;
+    }
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    if (!PatriciaTrieReadingUtils::hasBigrams(flags)) {
+        return NOT_A_DICT_POS;
+    }
+    PatriciaTrieReadingUtils::skipCharacters(dictRoot, flags, MAX_WORD_LENGTH, &pos);
+    if (PatriciaTrieReadingUtils::isTerminal(flags)) {
+        PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasChildrenInFlags(flags)) {
+        PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(dictRoot, flags, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
+    }
+    return pos;
+}
+
+int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode,
+        const int nodePos, const BinaryDictionaryInfo *const binaryDictionaryInfo,
+        const NodeFilter *const childrenFilter, DicNodeVector *childDicNodes) const {
+    const uint8_t *const dictRoot = binaryDictionaryInfo->getDictRoot();
+    int pos = nodePos;
+    const PatriciaTrieReadingUtils::NodeFlags flags =
+            PatriciaTrieReadingUtils::getFlagsAndAdvancePosition(dictRoot, &pos);
+    int mergedNodeCodePoints[MAX_WORD_LENGTH];
+    const int mergedNodeCodePointCount = PatriciaTrieReadingUtils::getCharsAndAdvancePosition(
+            dictRoot, flags, MAX_WORD_LENGTH, mergedNodeCodePoints, &pos);
+    const int probability = (PatriciaTrieReadingUtils::isTerminal(flags))?
+            PatriciaTrieReadingUtils::readProbabilityAndAdvancePosition(dictRoot, &pos)
+                    : NOT_A_PROBABILITY;
+    const int childrenPos = PatriciaTrieReadingUtils::hasChildrenInFlags(flags) ?
+            PatriciaTrieReadingUtils::readChildrenPositionAndAdvancePosition(
+                    dictRoot, flags, &pos) : NOT_A_DICT_POS;
+    if (PatriciaTrieReadingUtils::hasShortcutTargets(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipShortcuts(binaryDictionaryInfo, &pos);
+    }
+    if (PatriciaTrieReadingUtils::hasBigrams(flags)) {
+        BinaryDictionaryTerminalAttributesReadingUtils::skipExistingBigrams(
+                binaryDictionaryInfo, &pos);
+    }
+    if (!childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
+        childDicNodes->pushLeavingChild(dicNode, nodePos, childrenPos, probability,
+                PatriciaTrieReadingUtils::isTerminal(flags),
+                PatriciaTrieReadingUtils::hasChildrenInFlags(flags),
+                PatriciaTrieReadingUtils::isBlacklisted(flags) ||
+                        PatriciaTrieReadingUtils::isNotAWord(flags),
+                mergedNodeCodePointCount, mergedNodeCodePoints);
+    }
+    return pos;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
new file mode 100644
index 0000000..71f256e
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_policy.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PATRICIA_TRIE_POLICY_H
+#define LATINIME_PATRICIA_TRIE_POLICY_H
+
+#include "defines.h"
+#include "suggest/core/policy/dictionary_structure_policy.h"
+
+namespace latinime {
+
+class PatriciaTriePolicy : public DictionaryStructurePolicy {
+ public:
+    static AK_FORCE_INLINE const PatriciaTriePolicy *getInstance() {
+        return &sInstance;
+    }
+
+    AK_FORCE_INLINE int getRootPosition() const {
+        return 0;
+    }
+
+    void createAndGetAllChildNodes(const DicNode *const dicNode,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+
+    int getCodePointsAndProbabilityAndReturnCodePointCount(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int terminalNodePos, const int maxCodePointCount, int *const outCodePoints,
+            int *const outUnigramProbability) const;
+
+    int getTerminalNodePositionOfWord(
+            const BinaryDictionaryInfo *const binaryDictionaryInfo, const int *const inWord,
+            const int length, const bool forceLowerCaseSearch) const;
+
+    int getUnigramProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+    int getShortcutPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+    int getBigramsPositionOfNode(const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const int nodePos) const;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(PatriciaTriePolicy);
+    static const PatriciaTriePolicy sInstance;
+
+    PatriciaTriePolicy() {}
+    ~PatriciaTriePolicy() {}
+
+    int createAndGetLeavingChildNode(const DicNode *const dicNode, const int nodePos,
+            const BinaryDictionaryInfo *const binaryDictionaryInfo,
+            const NodeFilter *const nodeFilter, DicNodeVector *const childDicNodes) const;
+};
+} // namespace latinime
+#endif // LATINIME_PATRICIA_TRIE_POLICY_H
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
new file mode 100644
index 0000000..89e981d
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "suggest/policyimpl/dictionary/patricia_trie_reading_utils.h"
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+typedef PatriciaTrieReadingUtils PtReadingUtils;
+
+const PtReadingUtils::NodeFlags PtReadingUtils::MASK_GROUP_ADDRESS_TYPE = 0xC0;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+
+// Flag for single/multiple char group
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_MULTIPLE_CHARS = 0x20;
+// Flag for terminal groups
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_TERMINAL = 0x10;
+// Flag for shortcut targets presence
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_SHORTCUT_TARGETS = 0x08;
+// Flag for bigram presence
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_HAS_BIGRAMS = 0x04;
+// Flag for non-words (typically, shortcut only entries)
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_NOT_A_WORD = 0x02;
+// Flag for blacklist
+const PtReadingUtils::NodeFlags PtReadingUtils::FLAG_IS_BLACKLISTED = 0x01;
+
+/* static */ int PtReadingUtils::readChildrenPositionAndAdvancePosition(
+        const uint8_t *const buffer, const NodeFlags flags, int *const pos) {
+    const int base = *pos;
+    int offset = 0;
+    switch (MASK_GROUP_ADDRESS_TYPE & flags) {
+        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+            offset = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+            offset = ByteArrayUtils::readUint16AndAdvancePosition(buffer, pos);
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+            offset = ByteArrayUtils::readUint24AndAdvancePosition(buffer, pos);
+            break;
+        default:
+            // If we come here, it means we asked for the children of a word with
+            // no children.
+            return NOT_A_DICT_POS;
+    }
+    return base + offset;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
new file mode 100644
index 0000000..002c3f1
--- /dev/null
+++ b/native/jni/src/suggest/policyimpl/dictionary/patricia_trie_reading_utils.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_PATRICIA_TRIE_READING_UTILS_H
+#define LATINIME_PATRICIA_TRIE_READING_UTILS_H
+
+#include <stdint.h>
+
+#include "defines.h"
+#include "suggest/core/dictionary/byte_array_utils.h"
+
+namespace latinime {
+
+class PatriciaTrieReadingUtils {
+ public:
+    typedef uint8_t NodeFlags;
+
+    static AK_FORCE_INLINE int getGroupCountAndAdvancePosition(
+            const uint8_t *const buffer, int *const pos) {
+        const uint8_t firstByte = ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+        if (firstByte < 0x80) {
+            return firstByte;
+        } else {
+            return ((firstByte & 0x7F) << 8) ^ ByteArrayUtils::readUint8AndAdvancePosition(
+                    buffer, pos);
+        }
+    }
+
+    static AK_FORCE_INLINE NodeFlags getFlagsAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    }
+
+    static AK_FORCE_INLINE int getCodePointAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readCodePointAndAdvancePosition(buffer, pos);
+    }
+
+    // Returns the number of read characters.
+    static AK_FORCE_INLINE int getCharsAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, const int maxLength, int *const outBuffer, int *const pos) {
+        int length = 0;
+        if (hasMultipleChars(flags)) {
+            length = ByteArrayUtils::readStringAndAdvancePosition(buffer, maxLength, outBuffer,
+                    pos);
+        } else {
+            if (maxLength > 0) {
+                outBuffer[0] = getCodePointAndAdvancePosition(buffer, pos);
+                length = 1;
+            }
+        }
+        return length;
+    }
+
+    // Returns the number of skipped characters.
+    static AK_FORCE_INLINE int skipCharacters(const uint8_t *const buffer, const NodeFlags flags,
+            const int maxLength, int *const pos) {
+        if (hasMultipleChars(flags)) {
+            return ByteArrayUtils::advancePositionToBehindString(buffer, maxLength, pos);
+        } else {
+            if (maxLength > 0) {
+                getCodePointAndAdvancePosition(buffer, pos);
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    static AK_FORCE_INLINE int readProbabilityAndAdvancePosition(const uint8_t *const buffer,
+            int *const pos) {
+        return ByteArrayUtils::readUint8AndAdvancePosition(buffer, pos);
+    }
+
+    static int readChildrenPositionAndAdvancePosition(const uint8_t *const buffer,
+            const NodeFlags flags, int *const pos);
+
+    /**
+     * Node Flags
+     */
+    static AK_FORCE_INLINE bool isBlacklisted(const NodeFlags flags) {
+        return (flags & FLAG_IS_BLACKLISTED) != 0;
+    }
+
+    static AK_FORCE_INLINE bool isNotAWord(const NodeFlags flags) {
+        return (flags & FLAG_IS_NOT_A_WORD) != 0;
+    }
+
+    static AK_FORCE_INLINE bool isTerminal(const NodeFlags flags) {
+        return (flags & FLAG_IS_TERMINAL) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasShortcutTargets(const NodeFlags flags) {
+        return (flags & FLAG_HAS_SHORTCUT_TARGETS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasBigrams(const NodeFlags flags) {
+        return (flags & FLAG_HAS_BIGRAMS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasMultipleChars(const NodeFlags flags) {
+        return (flags & FLAG_HAS_MULTIPLE_CHARS) != 0;
+    }
+
+    static AK_FORCE_INLINE bool hasChildrenInFlags(const NodeFlags flags) {
+        return FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(PatriciaTrieReadingUtils);
+
+    static const NodeFlags MASK_GROUP_ADDRESS_TYPE;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_NOADDRESS;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
+    static const NodeFlags FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+
+    static const NodeFlags FLAG_HAS_MULTIPLE_CHARS;
+    static const NodeFlags FLAG_IS_TERMINAL;
+    static const NodeFlags FLAG_HAS_SHORTCUT_TARGETS;
+    static const NodeFlags FLAG_HAS_BIGRAMS;
+    static const NodeFlags FLAG_IS_NOT_A_WORD;
+    static const NodeFlags FLAG_IS_BLACKLISTED;
+};
+} // namespace latinime
+#endif /* LATINIME_PATRICIA_TRIE_NODE_READING_UTILS_H */
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
index f879892..ecceb60 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.cpp
@@ -22,31 +22,36 @@
 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;
+// TODO: Unlimit max cache dic node size
+const int ScoringParams::MAX_CACHE_DIC_NODE_SIZE = 170;
+const int ScoringParams::MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT = 310;
 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::PROXIMITY_COST = 0.095f;
+const float ScoringParams::FIRST_CHAR_PROXIMITY_COST = 0.102f;
+const float ScoringParams::FIRST_PROXIMITY_COST = 0.019f;
 const float ScoringParams::OMISSION_COST = 0.458f;
 const float ScoringParams::OMISSION_COST_SAME_CHAR = 0.491f;
 const float ScoringParams::OMISSION_COST_FIRST_CHAR = 0.582f;
 const float ScoringParams::INSERTION_COST = 0.730f;
+const float ScoringParams::TERMINAL_INSERTION_COST = 0.93f;
 const float ScoringParams::INSERTION_COST_SAME_CHAR = 0.586f;
+const float ScoringParams::INSERTION_COST_PROXIMITY_CHAR = 0.70f;
 const float ScoringParams::INSERTION_COST_FIRST_CHAR = 0.623f;
-const float ScoringParams::TRANSPOSITION_COST = 0.516f;
+const float ScoringParams::TRANSPOSITION_COST = 0.526f;
 const float ScoringParams::SPACE_SUBSTITUTION_COST = 0.319f;
 const float ScoringParams::ADDITIONAL_PROXIMITY_COST = 0.380f;
-const float ScoringParams::SUBSTITUTION_COST = 0.403f;
+const float ScoringParams::SUBSTITUTION_COST = 0.383f;
 const float ScoringParams::COST_NEW_WORD = 0.042f;
 const float ScoringParams::COST_SECOND_OR_LATER_WORD_FIRST_CHAR_UPPERCASE = 0.25f;
 const float ScoringParams::DISTANCE_WEIGHT_LANGUAGE = 1.123f;
 const float ScoringParams::COST_FIRST_LOOKAHEAD = 0.545f;
 const float ScoringParams::COST_LOOKAHEAD = 0.073f;
-const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.105f;
-const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.038f;
-const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.444f;
+const float ScoringParams::HAS_PROXIMITY_TERMINAL_COST = 0.093f;
+const float ScoringParams::HAS_EDIT_CORRECTION_TERMINAL_COST = 0.041f;
+const float ScoringParams::HAS_MULTI_WORD_TERMINAL_COST = 0.447f;
 const float ScoringParams::TYPING_BASE_OUTPUT_SCORE = 1.0f;
 const float ScoringParams::TYPING_MAX_OUTPUT_SCORE_PER_INPUT = 0.1f;
-const float ScoringParams::NORMALIZED_SPATIAL_DISTANCE_THRESHOLD_FOR_EDIT = 0.06f;
+const float ScoringParams::NORMALIZED_SPATIAL_DISTANCE_THRESHOLD_FOR_EDIT = 0.045f;
 } // namespace latinime
diff --git a/native/jni/src/suggest/policyimpl/typing/scoring_params.h b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
index 53ac999..7d4b5c3 100644
--- a/native/jni/src/suggest/policyimpl/typing/scoring_params.h
+++ b/native/jni/src/suggest/policyimpl/typing/scoring_params.h
@@ -29,6 +29,7 @@
     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 MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT;
     static const int THRESHOLD_SHORT_WORD_LENGTH;
 
     // Numerically optimized parameters (currently for tap typing only).
@@ -36,12 +37,15 @@
     // TODO: explore optimization of gesture parameters.
     static const float DISTANCE_WEIGHT_LENGTH;
     static const float PROXIMITY_COST;
+    static const float FIRST_CHAR_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 TERMINAL_INSERTION_COST;
     static const float INSERTION_COST_SAME_CHAR;
+    static const float INSERTION_COST_PROXIMITY_CHAR;
     static const float INSERTION_COST_FIRST_CHAR;
     static const float TRANSPOSITION_COST;
     static const float SPACE_SUBSTITUTION_COST;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
index 90e2133..56ffcc9 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_scoring.h
@@ -55,10 +55,10 @@
             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);
+        const float score = ScoringParams::TYPING_BASE_OUTPUT_SCORE
+                - compoundDistance / maxDistance
+                + (forceCommit ? ScoringParams::AUTOCORRECT_OUTPUT_THRESHOLD : 0.0f);
+        return static_cast<int>(score * SUGGEST_INTERFACE_OUTPUT_SCALE);
     }
 
     AK_FORCE_INLINE float getDoubleLetterDemotionDistanceCost(const int terminalIndex,
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 12110d5..89e53f4 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -19,14 +19,15 @@
 
 #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/layout/proximity_info_state.h"
+#include "suggest/core/layout/proximity_info_utils.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/policyimpl/typing/scoring_params.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 class TypingTraversal : public Traversal {
@@ -64,9 +65,9 @@
         }
         const int point0Index = dicNode->getInputIndex(0);
         const int currentBaseLowerCodePoint =
-                toBaseLowerCase(childDicNode->getNodeCodePoint());
+                CharUtils::toBaseLowerCase(childDicNode->getNodeCodePoint());
         const int typedBaseLowerCodePoint =
-                toBaseLowerCase(traverseSession->getProximityInfoState(0)
+                CharUtils::toBaseLowerCase(traverseSession->getProximityInfoState(0)
                         ->getPrimaryCodePointAt(point0Index));
         return (currentBaseLowerCodePoint != typedBaseLowerCodePoint);
     }
@@ -136,7 +137,7 @@
         return ScoringParams::MAX_SPATIAL_DISTANCE;
     }
 
-    AK_FORCE_INLINE bool allowPartialCommit() const {
+    AK_FORCE_INLINE bool autoCorrectsToMultiWordSuggestionIfTop() const {
         return true;
     }
 
@@ -147,11 +148,12 @@
     AK_FORCE_INLINE bool sameAsTyped(
             const DicTraverseSession *const traverseSession, const DicNode *const dicNode) const {
         return traverseSession->getProximityInfoState(0)->sameAsTyped(
-                dicNode->getOutputWordBuf(), dicNode->getDepth());
+                dicNode->getOutputWordBuf(), dicNode->getNodeCodePointCount());
     }
 
-    AK_FORCE_INLINE int getMaxCacheSize() const {
-        return ScoringParams::MAX_CACHE_DIC_NODE_SIZE;
+    AK_FORCE_INLINE int getMaxCacheSize(const int inputSize) const {
+        return (inputSize <= 1) ? ScoringParams::MAX_CACHE_DIC_NODE_SIZE_FOR_SINGLE_POINT
+                : ScoringParams::MAX_CACHE_DIC_NODE_SIZE;
     }
 
     AK_FORCE_INLINE bool isPossibleOmissionChildNode(
@@ -159,7 +161,7 @@
             const DicNode *const dicNode) const {
         const ProximityType proximityType =
                 getProximityType(traverseSession, parentDicNode, dicNode);
-        if (!DicNodeUtils::isProximityChar(proximityType)) {
+        if (!ProximityInfoUtils::isMatchOrProximityChar(proximityType)) {
             return false;
         }
         return true;
@@ -171,8 +173,8 @@
             return false;
         }
         const int c = dicNode->getOutputWordBuf()[0];
-        const bool shortCappedWord = dicNode->getDepth()
-                < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && isAsciiUpper(c);
+        const bool shortCappedWord = dicNode->getNodeCodePointCount()
+                < ScoringParams::THRESHOLD_SHORT_WORD_LENGTH && CharUtils::isAsciiUpper(c);
         return !shortCappedWord
                 || probability >= ScoringParams::THRESHOLD_NEXT_WORD_PROBABILITY_FOR_CAPPED;
     }
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
index e4c69d1..408b12a 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.cpp
@@ -44,6 +44,7 @@
             break;
         case CT_SUBSTITUTION:
         case CT_INSERTION:
+        case CT_TERMINAL_INSERTION:
         case CT_TRANSPOSITION:
             return ET_EDIT_CORRECTION;
         case CT_NEW_WORD_SPACE_OMITTION:
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index 3938c0e..7cddb08 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -18,11 +18,12 @@
 #define LATINIME_TYPING_WEIGHTING_H
 
 #include "defines.h"
-#include "suggest_utils.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/layout/touch_position_correction_utils.h"
 #include "suggest/core/policy/weighting.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/policyimpl/typing/scoring_params.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -54,7 +55,7 @@
         const bool isZeroCostOmission = parentDicNode->isZeroCostOmission();
         const bool sameCodePoint = dicNode->isSameNodeCodePoint(parentDicNode);
         // If the traversal omitted the first letter then the dicNode should now be on the second.
-        const bool isFirstLetterOmission = dicNode->getDepth() == 2;
+        const bool isFirstLetterOmission = dicNode->getNodeCodePointCount() == 2;
         float cost = 0.0f;
         if (isZeroCostOmission) {
             cost = 0.0f;
@@ -74,15 +75,18 @@
         // the keyboard (like accented letters)
         const float normalizedSquaredLength = traverseSession->getProximityInfoState(0)
                 ->getPointToKeyLength(pointIndex, dicNode->getNodeCodePoint());
-        const float normalizedDistance = SuggestUtils::getSweetSpotFactor(
+        const float normalizedDistance = TouchPositionCorrectionUtils::getSweetSpotFactor(
                 traverseSession->isTouchPositionCorrectionEnabled(), normalizedSquaredLength);
         const float weightedDistance = ScoringParams::DISTANCE_WEIGHT_LENGTH * normalizedDistance;
 
         const bool isFirstChar = pointIndex == 0;
         const bool isProximity = isProximityDicNode(traverseSession, dicNode);
-        float cost = isProximity ? (isFirstChar ? ScoringParams::FIRST_PROXIMITY_COST
+        float cost = isProximity ? (isFirstChar ? ScoringParams::FIRST_CHAR_PROXIMITY_COST
                 : ScoringParams::PROXIMITY_COST) : 0.0f;
-        if (dicNode->getDepth() == 2) {
+        if (isProximity && dicNode->getProximityCorrectionCount() == 0) {
+            cost += ScoringParams::FIRST_PROXIMITY_COST;
+        }
+        if (dicNode->getNodeCodePointCount() == 2) {
             // At the second character of the current word, we check if the first char is uppercase
             // and the word is a second or later word of a multiple word suggestion. We demote it
             // if so.
@@ -98,9 +102,9 @@
     bool isProximityDicNode(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode) const {
         const int pointIndex = dicNode->getInputIndex(0);
-        const int primaryCodePoint = toBaseLowerCase(
+        const int primaryCodePoint = CharUtils::toBaseLowerCase(
                 traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(pointIndex));
-        const int dicNodeChar = toBaseLowerCase(dicNode->getNodeCodePoint());
+        const int dicNodeChar = CharUtils::toBaseLowerCase(dicNode->getNodeCodePoint());
         return primaryCodePoint != dicNodeChar;
     }
 
@@ -121,31 +125,37 @@
 
     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 int16_t insertedPointIndex = parentDicNode->getInputIndex(0);
+        const int prevCodePoint = traverseSession->getProximityInfoState(0)->getPrimaryCodePointAt(
+                insertedPointIndex);
         const int currentCodePoint = dicNode->getNodeCodePoint();
         const bool sameCodePoint = prevCodePoint == currentCodePoint;
+        const bool existsAdjacentProximityChars = traverseSession->getProximityInfoState(0)
+                ->existsAdjacentProximityChars(insertedPointIndex);
         const float dist = traverseSession->getProximityInfoState(0)->getPointToKeyLength(
-                parentPointIndex + 1, currentCodePoint);
+                insertedPointIndex + 1, dicNode->getNodeCodePoint());
         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);
+        const bool singleChar = dicNode->getNodeCodePointCount() == 1;
+        float cost = (singleChar ? ScoringParams::INSERTION_COST_FIRST_CHAR : 0.0f);
+        if (sameCodePoint) {
+            cost += ScoringParams::INSERTION_COST_SAME_CHAR;
+        } else if (existsAdjacentProximityChars) {
+            cost += ScoringParams::INSERTION_COST_PROXIMITY_CHAR;
+        } else {
+            cost += ScoringParams::INSERTION_COST;
+        }
         return cost + weightedDistance;
     }
 
-    float getNewWordCost(const DicTraverseSession *const traverseSession,
-            const DicNode *const dicNode) const {
+    float getNewWordSpatialCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode, DicNode_InputStateG *inputStateG) const {
         return ScoringParams::COST_NEW_WORD * traverseSession->getMultiWordCostMultiplier();
     }
 
-    float getNewWordBigramCost(const DicTraverseSession *const traverseSession,
+    float getNewWordBigramLanguageCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode,
             MultiBigramMap *const multiBigramMap) const {
-        return DicNodeUtils::getBigramNodeImprobability(traverseSession->getOffsetDict(),
+        return DicNodeUtils::getBigramNodeImprobability(traverseSession->getBinaryDictionaryInfo(),
                 dicNode, multiBigramMap) * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
     }
 
@@ -162,9 +172,16 @@
 
     float getTerminalLanguageCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, const float dicNodeLanguageImprobability) const {
-        const float languageImprobability = (dicNode->isExactMatch()) ?
-                0.0f : dicNodeLanguageImprobability;
-        return languageImprobability * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
+        return dicNodeLanguageImprobability * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
+    }
+
+    float getTerminalInsertionCost(const DicTraverseSession *const traverseSession,
+            const DicNode *const dicNode) const {
+        const int inputIndex = dicNode->getInputIndex(0);
+        const int inputSize = traverseSession->getInputSize();
+        ASSERT(inputIndex < inputSize);
+        // TODO: Implement more efficient logic
+        return  ScoringParams::TERMINAL_INSERTION_COST * (inputSize - inputIndex);
     }
 
     AK_FORCE_INLINE bool needsToNormalizeCompoundDistance() const {
diff --git a/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
index ec14574..81614bc 100644
--- a/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
+++ b/native/jni/src/suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h
@@ -17,8 +17,8 @@
 #ifndef LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
 #define LATINIME_DAEMARU_LEVENSHTEIN_EDIT_DISTANCE_POLICY_H
 
-#include "char_utils.h"
 #include "suggest/policyimpl/utils/edit_distance_policy.h"
+#include "utils/char_utils.h"
 
 namespace latinime {
 
@@ -31,8 +31,8 @@
     ~DamerauLevenshteinEditDistancePolicy() {}
 
     AK_FORCE_INLINE float getSubstitutionCost(const int index0, const int index1) const {
-        const int c0 = toBaseLowerCase(mString0[index0]);
-        const int c1 = toBaseLowerCase(mString1[index1]);
+        const int c0 = CharUtils::toBaseLowerCase(mString0[index0]);
+        const int c1 = CharUtils::toBaseLowerCase(mString1[index1]);
         return (c0 == c1) ? 0.0f : 1.0f;
     }
 
@@ -45,10 +45,10 @@
     }
 
     AK_FORCE_INLINE bool allowTransposition(const int index0, const int index1) const {
-        const int c0 = toBaseLowerCase(mString0[index0]);
-        const int c1 = toBaseLowerCase(mString1[index1]);
-        if (index0 > 0 && index1 > 0 && c0 == toBaseLowerCase(mString1[index1 - 1])
-                && c1 == toBaseLowerCase(mString0[index0 - 1])) {
+        const int c0 = CharUtils::toBaseLowerCase(mString0[index0]);
+        const int c1 = CharUtils::toBaseLowerCase(mString1[index1]);
+        if (index0 > 0 && index1 > 0 && c0 == CharUtils::toBaseLowerCase(mString1[index1 - 1])
+                && c1 == CharUtils::toBaseLowerCase(mString0[index0 - 1])) {
             return true;
         }
         return false;
diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance.h b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
index cbbd668..0871c37 100644
--- a/native/jni/src/suggest/policyimpl/utils/edit_distance.h
+++ b/native/jni/src/suggest/policyimpl/utils/edit_distance.h
@@ -62,6 +62,26 @@
         return dp[(beforeLength + 1) * (afterLength + 1) - 1];
     }
 
+    AK_FORCE_INLINE static void dumpEditDistance10ForDebug(const float *const editDistanceTable,
+            const int editDistanceTableWidth, const int outputLength) {
+        if (DEBUG_DICT) {
+            AKLOGI("EditDistanceTable");
+            for (int i = 0; i <= 10; ++i) {
+                float c[11];
+                for (int j = 0; j <= 10; ++j) {
+                    if (j < editDistanceTableWidth + 1 && i < outputLength + 1) {
+                        c[j] = (editDistanceTable + i * (editDistanceTableWidth + 1))[j];
+                    } else {
+                        c[j] = -1.0f;
+                    }
+                }
+                AKLOGI("[ %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f ]",
+                        c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]);
+                (void)c; // To suppress compiler warning
+            }
+        }
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(EditDistance);
 };
diff --git a/native/jni/src/suggest_utils.h b/native/jni/src/suggest_utils.h
deleted file mode 100644
index e053dd6..0000000
--- a/native/jni/src/suggest_utils.h
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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_UTILS_H
-#define LATINIME_SUGGEST_UTILS_H
-
-#include "defines.h"
-#include "proximity_info_params.h"
-
-namespace latinime {
-class SuggestUtils {
- public:
-    // TODO: (OLD) Remove
-    static float getLengthScalingFactor(const float normalizedSquaredDistance) {
-        // Promote or demote the score according to the distance from the sweet spot
-        static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f;
-        static const float B = 1.0f;
-        static const float C = 0.5f;
-        static const float MIN = 0.3f;
-        static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS;
-        static const float R2 = HALF_SCORE_SQUARED_RADIUS;
-        const float x = normalizedSquaredDistance / static_cast<float>(
-                ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
-        const float factor = max((x < R1)
-                ? (A * (R1 - x) + B * x) / R1
-                : (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN);
-        // factor is a piecewise linear function like:
-        // A -_                  .
-        //     ^-_               .
-        // B      \              .
-        //         \_            .
-        // C         ------------.
-        //                       .
-        // 0   R1 R2             .
-        return factor;
-    }
-
-    static float getSweetSpotFactor(const bool isTouchPositionCorrectionEnabled,
-            const float normalizedSquaredDistance) {
-        // Promote or demote the score according to the distance from the sweet spot
-        static const float A = 0.0f;
-        static const float B = 0.24f;
-        static const float C = 1.20f;
-        static const float R0 = 0.0f;
-        static const float R1 = 0.25f; // Sweet spot
-        static const float R2 = 1.0f;
-        const float x = normalizedSquaredDistance;
-        if (!isTouchPositionCorrectionEnabled) {
-            return min(C, x);
-        }
-
-        // factor is a piecewise linear function like:
-        // C        -------------.
-        //         /             .
-        // B      /              .
-        //      -/               .
-        // A _-^                 .
-        //                       .
-        //   R0 R1 R2            .
-
-        if (x < R0) {
-            return A;
-        } else if (x < R1) {
-            return (A * (R1 - x) + B * (x - R0)) / (R1 - R0);
-        } else if (x < R2) {
-            return (B * (R2 - x) + C * (x - R1)) / (R2 - R1);
-        } else {
-            return C;
-        }
-    }
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(SuggestUtils);
-};
-} // namespace latinime
-#endif // LATINIME_SUGGEST_UTILS_H
diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h
deleted file mode 100644
index 92ef71c..0000000
--- a/native/jni/src/terminal_attributes.h
+++ /dev/null
@@ -1,85 +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_TERMINAL_ATTRIBUTES_H
-#define LATINIME_TERMINAL_ATTRIBUTES_H
-
-#include <stdint.h>
-#include "binary_format.h"
-
-namespace latinime {
-
-/**
- * This class encapsulates information about a terminal that allows to
- * retrieve local node attributes like the list of shortcuts without
- * exposing the format structure to the client.
- */
-class TerminalAttributes {
- public:
-    class ShortcutIterator {
-     public:
-        ShortcutIterator(const uint8_t *dict, const int pos, const uint8_t flags)
-                : mDict(dict), mPos(pos),
-                  mHasNextShortcutTarget(0 != (flags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS)) {
-        }
-
-        inline bool hasNextShortcutTarget() const {
-            return mHasNextShortcutTarget;
-        }
-
-        // Gets the shortcut target itself as an int string. For parameters and return value
-        // see BinaryFormat::getWordAtAddress.
-        inline int getNextShortcutTarget(const int maxDepth, int *outWord, int *outFreq) {
-            const int shortcutFlags = BinaryFormat::getFlagsAndForwardPointer(mDict, &mPos);
-            mHasNextShortcutTarget = 0 != (shortcutFlags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT);
-            unsigned int i;
-            for (i = 0; i < MAX_WORD_LENGTH; ++i) {
-                const int codePoint = BinaryFormat::getCodePointAndForwardPointer(mDict, &mPos);
-                if (NOT_A_CODE_POINT == codePoint) break;
-                outWord[i] = codePoint;
-            }
-            *outFreq = BinaryFormat::getAttributeProbabilityFromFlags(shortcutFlags);
-            return i;
-        }
-
-     private:
-        const uint8_t *const mDict;
-        int mPos;
-        bool mHasNextShortcutTarget;
-    };
-
-    TerminalAttributes(const uint8_t *const dict, const uint8_t flags, const int pos)
-            : mDict(dict), mFlags(flags), mStartPos(pos) {
-    }
-
-    inline ShortcutIterator getShortcutIterator() const {
-        // The size of the shortcuts is stored here so that the whole shortcut chunk can be
-        // skipped quickly, so we ignore it.
-        return ShortcutIterator(mDict, mStartPos + BinaryFormat::SHORTCUT_LIST_SIZE_SIZE, mFlags);
-    }
-
-    bool isBlacklistedOrNotAWord() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mFlags);
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
-    const uint8_t *const mDict;
-    const uint8_t mFlags;
-    const int mStartPos;
-};
-} // namespace latinime
-#endif // LATINIME_TERMINAL_ATTRIBUTES_H
diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp
deleted file mode 100644
index a672294..0000000
--- a/native/jni/src/unigram_dictionary.cpp
+++ /dev/null
@@ -1,988 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstring>
-
-#define LOG_TAG "LatinIME: unigram_dictionary.cpp"
-
-#include "binary_format.h"
-#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"
-#include "words_priority_queue.h"
-#include "words_priority_queue_pool.h"
-
-namespace latinime {
-
-// TODO: check the header
-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), DICT_FLAGS(dictFlags) {
-    if (DEBUG_DICT) {
-        AKLOGI("UnigramDictionary - constructor");
-    }
-}
-
-UnigramDictionary::~UnigramDictionary() {
-}
-
-// TODO: This needs to take a const int* and not tinker with its contents
-static void addWord(int *word, int length, int probability, WordsPriorityQueue *queue, int type) {
-    queue->push(probability, word, length, type);
-}
-
-// 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 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;
-
-    // Search for the first char of some digraph
-    int lastDigraphIndex = -1;
-    const int thisChar = codes[i];
-    for (lastDigraphIndex = digraphsSize - 1; lastDigraphIndex >= 0; --lastDigraphIndex) {
-        if (thisChar == digraphs[lastDigraphIndex].first) break;
-    }
-    // No match: return early
-    if (lastDigraphIndex < 0) return 0;
-
-    // It's an interesting digraph if the second char matches too.
-    if (digraphs[lastDigraphIndex].second == codes[i + 1]) {
-        return digraphs[lastDigraphIndex].compositeGlyph;
-    } else {
-        return 0;
-    }
-}
-
-// Mostly the same arguments as the non-recursive version, except:
-// codes is the original value. It points to the start of the work buffer, and gets passed as is.
-// inputSize is the size of the user input (thus, it is the size of codesSrc).
-// codesDest is the current point in the work buffer.
-// codesSrc is the current point in the user-input, original, content-unmodified buffer.
-// codesRemain is the remaining size in codesSrc.
-void UnigramDictionary::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 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]));
-
-    const int startIndex = static_cast<int>(codesDest - codesBuffer);
-    if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) {
-        for (int i = 0; i < codesRemain; ++i) {
-            xCoordinatesBuffer[startIndex + i] = xcoordinates[codesBufferSize - codesRemain + i];
-            yCoordinatesBuffer[startIndex + i] = ycoordinates[codesBufferSize - codesRemain + i];
-            const int replacementCodePoint =
-                    getDigraphReplacement(codesSrc, i, codesRemain, digraphs, digraphsSize);
-            if (0 != replacementCodePoint) {
-                // Found a digraph. We will try both spellings. eg. the word is "pruefen"
-
-                // Copy the word up to the first char of the digraph, including proximity chars,
-                // and overwrite the primary code with the replacement code point. Then, continue
-                // processing on the remaining part of the word, skipping the second char of the
-                // digraph.
-                // In our example, copy "pru", replace "u" with the version with the diaeresis and
-                // continue running on "fen".
-                // Make i the index of the second char of the digraph for simplicity. Forgetting
-                // to do that results in an infinite recursion so take care!
-                ++i;
-                memcpy(codesDest, codesSrc, i * sizeof(codesDest[0]));
-                codesDest[i - 1] = replacementCodePoint;
-                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
-                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize,
-                        bigramMap, bigramFilter, useFullEditDistance, codesSrc + i + 1,
-                        codesRemain - i - 1, currentDepth + 1, codesDest + i, correction,
-                        queuePool, digraphs, digraphsSize);
-
-                // Copy the second char of the digraph in place, then continue processing on
-                // the remaining part of the word.
-                // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
-                memcpy(codesDest + i, codesSrc + i, sizeof(codesDest[0]));
-                getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
-                        codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize,
-                        bigramMap, bigramFilter, useFullEditDistance, codesSrc + i, codesRemain - i,
-                        currentDepth + 1, codesDest + i, correction, queuePool, digraphs,
-                        digraphsSize);
-                return;
-            }
-        }
-    }
-
-    // If we come here, we hit the end of the word: let's check it against the dictionary.
-    // In our example, we'll come here once for "prufen" and then once for "pruefen".
-    // If the word contains several digraphs, we'll come it for the product of them.
-    // eg. if the word is "ueberpruefen" we'll test, in order, against
-    // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen".
-    const unsigned int remainingBytes = sizeof(codesDest[0]) * codesRemain;
-    if (0 != remainingBytes) {
-        memcpy(codesDest, codesSrc, remainingBytes);
-        memcpy(&xCoordinatesBuffer[startIndex], &xcoordinates[codesBufferSize - codesRemain],
-                sizeof(xCoordinatesBuffer[0]) * codesRemain);
-        memcpy(&yCoordinatesBuffer[startIndex], &ycoordinates[codesBufferSize - codesRemain],
-                sizeof(yCoordinatesBuffer[0]) * codesRemain);
-    }
-
-    getWordSuggestions(proximityInfo, xCoordinatesBuffer, yCoordinatesBuffer, codesBuffer,
-            startIndex + codesRemain, bigramMap, bigramFilter, useFullEditDistance, correction,
-            queuePool);
-}
-
-// bigramMap contains the association <bigram address> -> <bigram probability>
-// bigramFilter is a bloom filter for fast rejection: see functions setInFilter and isInFilter
-// in bigram_dictionary.cpp
-int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-        const int *ycoordinates, const int *inputCodePoints, const int inputSize,
-        const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, int *outWords, int *frequencies, int *outputTypes) const {
-    WordsPriorityQueuePool queuePool(MAX_RESULTS, SUB_QUEUE_MAX_WORDS);
-    queuePool.clearAll();
-    Correction masterCorrection;
-    masterCorrection.resetCorrection();
-    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];
-        int yCoordinatesBuffer[inputSize];
-        getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
-                xCoordinatesBuffer, yCoordinatesBuffer, inputSize, bigramMap, bigramFilter,
-                useFullEditDistance, inputCodePoints, inputSize, 0, codesBuffer, &masterCorrection,
-                &queuePool, digraphs, digraphsSize);
-    } else { // Normal processing
-        getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, inputCodePoints, inputSize,
-                bigramMap, bigramFilter, useFullEditDistance, &masterCorrection, &queuePool);
-    }
-
-    PROF_START(20);
-    if (DEBUG_DICT) {
-        float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
-                masterCorrection.getPrimaryInputWord(), inputSize, 0, 0, 0);
-        ns += 0;
-        AKLOGI("Max normalized score = %f", ns);
-    }
-    const int suggestedWordsCount =
-            queuePool.getMasterQueue()->outputSuggestions(masterCorrection.getPrimaryInputWord(),
-                    inputSize, frequencies, outWords, outputTypes);
-
-    if (DEBUG_DICT) {
-        float ns = queuePool.getMasterQueue()->getHighestNormalizedScore(
-                masterCorrection.getPrimaryInputWord(), inputSize, 0, 0, 0);
-        ns += 0;
-        AKLOGI("Returning %d words", suggestedWordsCount);
-        /// Print the returned words
-        for (int j = 0; j < suggestedWordsCount; ++j) {
-            int *w = outWords + j * MAX_WORD_LENGTH;
-            char s[MAX_WORD_LENGTH];
-            for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i];
-            (void)s; // To suppress compiler warning
-            AKLOGI("%s %i", s, frequencies[j]);
-        }
-    }
-    PROF_END(20);
-    PROF_CLOSE;
-    return suggestedWordsCount;
-}
-
-void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-        const int *ycoordinates, const int *inputCodePoints, const int inputSize,
-        const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, Correction *correction, WordsPriorityQueuePool *queuePool)
-        const {
-    PROF_OPEN;
-    PROF_START(0);
-    PROF_END(0);
-
-    PROF_START(1);
-    getOneWordSuggestions(proximityInfo, xcoordinates, ycoordinates, inputCodePoints, bigramMap,
-            bigramFilter, useFullEditDistance, inputSize, correction, queuePool);
-    PROF_END(1);
-
-    PROF_START(2);
-    // Note: This line is intentionally left blank
-    PROF_END(2);
-
-    PROF_START(3);
-    // Note: This line is intentionally left blank
-    PROF_END(3);
-
-    PROF_START(4);
-    bool hasAutoCorrectionCandidate = false;
-    WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
-    if (masterQueue->size() > 0) {
-        float nsForMaster = masterQueue->getHighestNormalizedScore(
-                correction->getPrimaryInputWord(), inputSize, 0, 0, 0);
-        hasAutoCorrectionCandidate = (nsForMaster > START_TWO_WORDS_CORRECTION_THRESHOLD);
-    }
-    PROF_END(4);
-
-    PROF_START(5);
-    // Multiple word suggestions
-    if (SUGGEST_MULTIPLE_WORDS
-            && inputSize >= MIN_USER_TYPED_LENGTH_FOR_MULTIPLE_WORD_SUGGESTION) {
-        getSplitMultipleWordsSuggestions(proximityInfo, xcoordinates, ycoordinates, inputCodePoints,
-                useFullEditDistance, inputSize, correction, queuePool,
-                hasAutoCorrectionCandidate);
-    }
-    PROF_END(5);
-
-    PROF_START(6);
-    // Note: This line is intentionally left blank
-    PROF_END(6);
-
-    if (DEBUG_DICT) {
-        queuePool->dumpSubQueue1TopSuggestions();
-        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
-            WordsPriorityQueue *queue = queuePool->getSubQueue(FIRST_WORD_INDEX, i);
-            if (queue->size() > 0) {
-                WordsPriorityQueue::SuggestedWord *sw = queue->top();
-                const int score = sw->mScore;
-                const int *word = sw->mWord;
-                const int wordLength = sw->mWordLength;
-                float ns = Correction::RankingAlgorithm::calcNormalizedScore(
-                        correction->getPrimaryInputWord(), i, word, wordLength, score);
-                ns += 0;
-                AKLOGI("--- TOP SUB WORDS for %d --- %d %f [%d]", i, score, ns,
-                        (ns > TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD));
-                DUMP_WORD(correction->getPrimaryInputWord(), i);
-                DUMP_WORD(word, wordLength);
-            }
-        }
-    }
-}
-
-void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int *xCoordinates,
-        const int *yCoordinates, const int *codes, const int inputSize,
-        Correction *correction) const {
-    if (DEBUG_DICT) {
-        AKLOGI("initSuggest");
-        DUMP_WORD(codes, inputSize);
-    }
-    correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates);
-    const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH);
-    correction->initCorrection(proximityInfo, inputSize, maxDepth);
-}
-
-void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        const bool useFullEditDistance, const int inputSize,
-        Correction *correction, WordsPriorityQueuePool *queuePool) const {
-    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction);
-    getSuggestionCandidates(useFullEditDistance, inputSize, bigramMap, bigramFilter, correction,
-            queuePool, true /* doAutoCompletion */, DEFAULT_MAX_ERRORS, FIRST_WORD_INDEX);
-}
-
-void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance,
-        const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-        Correction *correction, WordsPriorityQueuePool *queuePool,
-        const bool doAutoCompletion, const int maxErrors, const int currentWordIndex) const {
-    uint8_t totalTraverseCount = correction->pushAndGetTotalTraverseCount();
-    if (DEBUG_DICT) {
-        AKLOGI("Traverse count %d", totalTraverseCount);
-    }
-    if (totalTraverseCount > MULTIPLE_WORDS_SUGGESTION_MAX_TOTAL_TRAVERSE_COUNT) {
-        if (DEBUG_DICT) {
-            AKLOGI("Abort traversing %d", totalTraverseCount);
-        }
-        return;
-    }
-    // TODO: Remove setCorrectionParams
-    correction->setCorrectionParams(0, 0, 0,
-            -1 /* spaceProximityPos */, -1 /* missingSpacePos */, useFullEditDistance,
-            doAutoCompletion, maxErrors);
-    int rootPosition = ROOT_POS;
-    // Get the number of children of root, then increment the position
-    int childCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &rootPosition);
-    int outputIndex = 0;
-
-    correction->initCorrectionState(rootPosition, childCount, (inputSize <= 0));
-
-    // Depth first search
-    while (outputIndex >= 0) {
-        if (correction->initProcessState(outputIndex)) {
-            int siblingPos = correction->getTreeSiblingPos(outputIndex);
-            int firstChildPos;
-
-            const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
-                    bigramMap, bigramFilter, correction, &childCount, &firstChildPos, &siblingPos,
-                    queuePool, currentWordIndex);
-            // Update next sibling pos
-            correction->setTreeSiblingPos(outputIndex, siblingPos);
-
-            if (needsToTraverseChildrenNodes) {
-                // Goes to child node
-                outputIndex = correction->goDownTree(outputIndex, childCount, firstChildPos);
-            }
-        } else {
-            // Goes to parent sibling node
-            outputIndex = correction->getTreeParentIndex(outputIndex);
-        }
-    }
-}
-
-void UnigramDictionary::onTerminal(const int probability,
-        const TerminalAttributes &terminalAttributes, Correction *correction,
-        WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
-        const int currentWordIndex) const {
-    const int inputIndex = correction->getInputIndex();
-    const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT;
-
-    int wordLength;
-    int *wordPointer;
-
-    if ((currentWordIndex == FIRST_WORD_INDEX) && addToMasterQueue) {
-        WordsPriorityQueue *masterQueue = queuePool->getMasterQueue();
-        const int finalProbability =
-                correction->getFinalProbability(probability, &wordPointer, &wordLength);
-
-        if (0 != finalProbability && !terminalAttributes.isBlacklistedOrNotAWord()) {
-            // If the probability is 0, we don't want to add this word. However we still
-            // want to add its shortcuts (including a possible whitelist entry) if any.
-            // Furthermore, if this is not a word (shortcut only for example) or a blacklisted
-            // entry then we never want to suggest this.
-            addWord(wordPointer, wordLength, finalProbability, masterQueue,
-                    Dictionary::KIND_CORRECTION);
-        }
-
-        const int shortcutProbability = finalProbability > 0 ? finalProbability - 1 : 0;
-        // Please note that the shortcut candidates will be added to the master queue only.
-        TerminalAttributes::ShortcutIterator iterator = terminalAttributes.getShortcutIterator();
-        while (iterator.hasNextShortcutTarget()) {
-            // TODO: addWord only supports weak ordering, meaning we have no means
-            // to control the order of the shortcuts relative to one another or to the word.
-            // We need to either modulate the probability of each shortcut according
-            // to its own shortcut probability or to make the queue
-            // so that the insert order is protected inside the queue for words
-            // with the same score. For the moment we use -1 to make sure the shortcut will
-            // never be in front of the word.
-            int shortcutTarget[MAX_WORD_LENGTH];
-            int shortcutFrequency;
-            const int shortcutTargetStringLength = iterator.getNextShortcutTarget(
-                    MAX_WORD_LENGTH, shortcutTarget, &shortcutFrequency);
-            int shortcutScore;
-            int kind;
-            if (shortcutFrequency == BinaryFormat::WHITELIST_SHORTCUT_PROBABILITY
-                    && correction->sameAsTyped()) {
-                shortcutScore = S_INT_MAX;
-                kind = Dictionary::KIND_WHITELIST;
-            } else {
-                shortcutScore = shortcutProbability;
-                kind = Dictionary::KIND_CORRECTION;
-            }
-            addWord(shortcutTarget, shortcutTargetStringLength, shortcutScore,
-                    masterQueue, kind);
-        }
-    }
-
-    // We only allow two words + other error correction for words with SUB_QUEUE_MIN_WORD_LENGTH
-    // or more length.
-    if (inputIndex >= SUB_QUEUE_MIN_WORD_LENGTH && addToSubQueue) {
-        WordsPriorityQueue *subQueue;
-        subQueue = queuePool->getSubQueue(currentWordIndex, inputIndex);
-        if (!subQueue) {
-            return;
-        }
-        const int finalProbability = correction->getFinalProbabilityForSubQueue(
-                probability, &wordPointer, &wordLength, inputIndex);
-        addWord(wordPointer, wordLength, finalProbability, subQueue, Dictionary::KIND_CORRECTION);
-    }
-}
-
-int UnigramDictionary::getSubStringSuggestion(
-        ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates,
-        const int *codes, const bool useFullEditDistance, Correction *correction,
-        WordsPriorityQueuePool *queuePool, const int inputSize,
-        const bool hasAutoCorrectionCandidate, const int currentWordIndex,
-        const int inputWordStartPos, const int inputWordLength,
-        const int outputWordStartPos, const bool isSpaceProximity, int *freqArray,
-        int *wordLengthArray, int *outputWord, int *outputWordLength) const {
-    if (inputWordLength > MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH) {
-        return FLAG_MULTIPLE_SUGGEST_ABORT;
-    }
-
-    /////////////////////////////////////////////
-    // safety net for multiple word suggestion //
-    // TODO: Remove this safety net            //
-    /////////////////////////////////////////////
-    int smallWordCount = 0;
-    int singleLetterWordCount = 0;
-    if (inputWordLength == 1) {
-        ++singleLetterWordCount;
-    }
-    if (inputWordLength <= 2) {
-        // small word == single letter or 2-letter word
-        ++smallWordCount;
-    }
-    for (int i = 0; i < currentWordIndex; ++i) {
-        const int length = wordLengthArray[i];
-        if (length == 1) {
-            ++singleLetterWordCount;
-            // Safety net to avoid suggesting sequential single letter words
-            if (i < (currentWordIndex - 1)) {
-                if (wordLengthArray[i + 1] == 1) {
-                    return FLAG_MULTIPLE_SUGGEST_ABORT;
-                }
-            } else if (inputWordLength == 1) {
-                return FLAG_MULTIPLE_SUGGEST_ABORT;
-            }
-        }
-        if (length <= 2) {
-            ++smallWordCount;
-        }
-        // Safety net to avoid suggesting multiple words with many (4 or more, for now) small words
-        if (singleLetterWordCount >= 3 || smallWordCount >= 4) {
-            return FLAG_MULTIPLE_SUGGEST_ABORT;
-        }
-    }
-    //////////////////////////////////////////////
-    // TODO: Remove the safety net above        //
-    //////////////////////////////////////////////
-
-    int *tempOutputWord = 0;
-    int nextWordLength = 0;
-    // TODO: Optimize init suggestion
-    initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
-            inputSize, correction);
-
-    int word[MAX_WORD_LENGTH];
-    int freq = getMostProbableWordLike(
-            inputWordStartPos, inputWordLength, correction, word);
-    if (freq > 0) {
-        nextWordLength = inputWordLength;
-        tempOutputWord = word;
-    } else if (!hasAutoCorrectionCandidate) {
-        if (inputWordStartPos > 0) {
-            const int offset = inputWordStartPos;
-            initSuggestions(proximityInfo, &xcoordinates[offset], &ycoordinates[offset],
-                    codes + offset, inputWordLength, correction);
-            queuePool->clearSubQueue(currentWordIndex);
-            // TODO: pass the bigram list for substring suggestion
-            getSuggestionCandidates(useFullEditDistance, inputWordLength,
-                    0 /* bigramMap */, 0 /* bigramFilter */, correction, queuePool,
-                    false /* doAutoCompletion */, MAX_ERRORS_FOR_TWO_WORDS, currentWordIndex);
-            if (DEBUG_DICT) {
-                if (currentWordIndex < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS) {
-                    AKLOGI("Dump word candidates(%d) %d", currentWordIndex, inputWordLength);
-                    for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
-                        queuePool->getSubQueue(currentWordIndex, i)->dumpTopWord();
-                    }
-                }
-            }
-        }
-        WordsPriorityQueue *queue = queuePool->getSubQueue(currentWordIndex, inputWordLength);
-        // TODO: Return the correct value depending on doAutoCompletion
-        if (!queue || queue->size() <= 0) {
-            return FLAG_MULTIPLE_SUGGEST_ABORT;
-        }
-        int score = 0;
-        const float ns = queue->getHighestNormalizedScore(
-                correction->getPrimaryInputWord(), inputWordLength,
-                &tempOutputWord, &score, &nextWordLength);
-        if (DEBUG_DICT) {
-            AKLOGI("NS(%d) = %f, Score = %d", currentWordIndex, ns, score);
-        }
-        // Two words correction won't be done if the score of the first word doesn't exceed the
-        // threshold.
-        if (ns < TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD
-                || nextWordLength < SUB_QUEUE_MIN_WORD_LENGTH) {
-            return FLAG_MULTIPLE_SUGGEST_SKIP;
-        }
-        freq = score >> (nextWordLength + TWO_WORDS_PLUS_OTHER_ERROR_CORRECTION_DEMOTION_DIVIDER);
-    }
-    if (DEBUG_DICT) {
-        AKLOGI("Freq(%d): %d, length: %d, input length: %d, input start: %d (%d)",
-                currentWordIndex, freq, nextWordLength, inputWordLength, inputWordStartPos,
-                (currentWordIndex > 0) ? wordLengthArray[0] : 0);
-    }
-    if (freq <= 0 || nextWordLength <= 0
-            || MAX_WORD_LENGTH <= (outputWordStartPos + nextWordLength)) {
-        return FLAG_MULTIPLE_SUGGEST_SKIP;
-    }
-    for (int i = 0; i < nextWordLength; ++i) {
-        outputWord[outputWordStartPos + i] = tempOutputWord[i];
-    }
-
-    // Put output values
-    freqArray[currentWordIndex] = freq;
-    // TODO: put output length instead of input length
-    wordLengthArray[currentWordIndex] = inputWordLength;
-    const int tempOutputWordLength = outputWordStartPos + nextWordLength;
-    if (outputWordLength) {
-        *outputWordLength = tempOutputWordLength;
-    }
-
-    if ((inputWordStartPos + inputWordLength) < inputSize) {
-        if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) {
-            return FLAG_MULTIPLE_SUGGEST_SKIP;
-        }
-        outputWord[tempOutputWordLength] = KEYCODE_SPACE;
-        if (outputWordLength) {
-            ++*outputWordLength;
-        }
-    } else if (currentWordIndex >= 1) {
-        // TODO: Handle 3 or more words
-        const int pairFreq = correction->getFreqForSplitMultipleWords(
-                freqArray, wordLengthArray, currentWordIndex + 1, isSpaceProximity, outputWord);
-        if (DEBUG_DICT) {
-            DUMP_WORD(outputWord, tempOutputWordLength);
-            for (int i = 0; i < currentWordIndex + 1; ++i) {
-                AKLOGI("Split %d,%d words: freq = %d, length = %d", i, currentWordIndex + 1,
-                        freqArray[i], wordLengthArray[i]);
-            }
-            AKLOGI("Split two words: freq = %d, length = %d, %d, isSpace ? %d", pairFreq,
-                    inputSize, tempOutputWordLength, isSpaceProximity);
-        }
-        addWord(outputWord, tempOutputWordLength, pairFreq, queuePool->getMasterQueue(),
-                Dictionary::KIND_CORRECTION);
-    }
-    return FLAG_MULTIPLE_SUGGEST_CONTINUE;
-}
-
-void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const bool useFullEditDistance, const int inputSize, Correction *correction,
-        WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate,
-        const int startInputPos, const int startWordIndex, const int outputWordLength,
-        int *freqArray, int *wordLengthArray, int *outputWord) const {
-    if (startWordIndex >= (MULTIPLE_WORDS_SUGGESTION_MAX_WORDS - 1)) {
-        // Return if the last word index
-        return;
-    }
-    if (startWordIndex >= 1
-            && (hasAutoCorrectionCandidate
-                    || inputSize < MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION)) {
-        // Do not suggest 3+ words if already has auto correction candidate
-        return;
-    }
-    for (int i = startInputPos + 1; i < inputSize; ++i) {
-        if (DEBUG_CORRECTION_FREQ) {
-            AKLOGI("Multi words(%d), start in %d sep %d start out %d",
-                    startWordIndex, startInputPos, i, outputWordLength);
-            DUMP_WORD(outputWord, outputWordLength);
-        }
-        int tempOutputWordLength = 0;
-        // Current word
-        int inputWordStartPos = startInputPos;
-        int inputWordLength = i - startInputPos;
-        const int suggestionFlag = getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates,
-                codes, useFullEditDistance, correction, queuePool, inputSize,
-                hasAutoCorrectionCandidate, startWordIndex, inputWordStartPos, inputWordLength,
-                outputWordLength, true /* not used */, freqArray, wordLengthArray, outputWord,
-                &tempOutputWordLength);
-        if (suggestionFlag == FLAG_MULTIPLE_SUGGEST_ABORT) {
-            // TODO: break here
-            continue;
-        } else if (suggestionFlag == FLAG_MULTIPLE_SUGGEST_SKIP) {
-            continue;
-        }
-
-        if (DEBUG_CORRECTION_FREQ) {
-            AKLOGI("Do missing space correction");
-        }
-        // Next word
-        // Missing space
-        inputWordStartPos = i;
-        inputWordLength = inputSize - i;
-        if (getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
-                useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
-                startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
-                false /* missing space */, freqArray, wordLengthArray, outputWord, 0)
-                        != FLAG_MULTIPLE_SUGGEST_CONTINUE) {
-            getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
-                    useFullEditDistance, inputSize, correction, queuePool,
-                    hasAutoCorrectionCandidate, inputWordStartPos, startWordIndex + 1,
-                    tempOutputWordLength, freqArray, wordLengthArray, outputWord);
-        }
-
-        // Mistyped space
-        ++inputWordStartPos;
-        --inputWordLength;
-
-        if (inputWordLength <= 0) {
-            continue;
-        }
-
-        const int x = xcoordinates[inputWordStartPos - 1];
-        const int y = ycoordinates[inputWordStartPos - 1];
-        if (!proximityInfo->hasSpaceProximity(x, y)) {
-            continue;
-        }
-
-        if (DEBUG_CORRECTION_FREQ) {
-            AKLOGI("Do mistyped space correction");
-        }
-        getSubStringSuggestion(proximityInfo, xcoordinates, ycoordinates, codes,
-                useFullEditDistance, correction, queuePool, inputSize, hasAutoCorrectionCandidate,
-                startWordIndex + 1, inputWordStartPos, inputWordLength, tempOutputWordLength,
-                true /* mistyped space */, freqArray, wordLengthArray, outputWord, 0);
-    }
-}
-
-void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo,
-        const int *xcoordinates, const int *ycoordinates, const int *codes,
-        const bool useFullEditDistance, const int inputSize,
-        Correction *correction, WordsPriorityQueuePool *queuePool,
-        const bool hasAutoCorrectionCandidate) const {
-    if (inputSize >= MAX_WORD_LENGTH) return;
-    if (DEBUG_DICT) {
-        AKLOGI("--- Suggest multiple words");
-    }
-
-    // Allocating fixed length array on stack
-    int outputWord[MAX_WORD_LENGTH];
-    int freqArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
-    int wordLengthArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
-    const int outputWordLength = 0;
-    const int startInputPos = 0;
-    const int startWordIndex = 0;
-    getMultiWordsSuggestionRec(proximityInfo, xcoordinates, ycoordinates, codes,
-            useFullEditDistance, inputSize, correction, queuePool, hasAutoCorrectionCandidate,
-            startInputPos, startWordIndex, outputWordLength, freqArray, wordLengthArray,
-            outputWord);
-}
-
-// Wrapper for getMostProbableWordLikeInner, which matches it to the previous
-// interface.
-int UnigramDictionary::getMostProbableWordLike(const int startInputIndex, const int inputSize,
-        Correction *correction, int *word) const {
-    int inWord[inputSize];
-    for (int i = 0; i < inputSize; ++i) {
-        inWord[i] = correction->getPrimaryCodePointAt(startInputIndex + i);
-    }
-    return getMostProbableWordLikeInner(inWord, inputSize, word);
-}
-
-// This function will take the position of a character array within a CharGroup,
-// and check it actually like-matches the word in inWord starting at startInputIndex,
-// that is, it matches it with case and accents squashed.
-// The function returns true if there was a full match, false otherwise.
-// The function will copy on-the-fly the characters in the CharGroup to outNewWord.
-// It will also place the end position of the array in outPos; in outInputIndex,
-// it will place the index of the first char AFTER the match if there was a match,
-// and the initial position if there was not. It makes sense because if there was
-// a match we want to continue searching, but if there was not, we want to go to
-// the next CharGroup.
-// In and out parameters may point to the same location. This function takes care
-// not to use any input parameters after it wrote into its outputs.
-static inline bool testCharGroupForContinuedLikeness(const uint8_t flags,
-        const uint8_t *const root, const int startPos, const int *const inWord,
-        const int startInputIndex, const int inputSize, int *outNewWord, int *outInputIndex,
-        int *outPos) {
-    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
-    int pos = startPos;
-    int codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-    int baseChar = toBaseLowerCase(codePoint);
-    const int wChar = toBaseLowerCase(inWord[startInputIndex]);
-
-    if (baseChar != wChar) {
-        *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos;
-        *outInputIndex = startInputIndex;
-        return false;
-    }
-    int inputIndex = startInputIndex;
-    outNewWord[inputIndex] = codePoint;
-    if (hasMultipleChars) {
-        codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-        while (NOT_A_CODE_POINT != codePoint) {
-            baseChar = toBaseLowerCase(codePoint);
-            if (inputIndex + 1 >= inputSize || toBaseLowerCase(inWord[++inputIndex]) != baseChar) {
-                *outPos = BinaryFormat::skipOtherCharacters(root, pos);
-                *outInputIndex = startInputIndex;
-                return false;
-            }
-            outNewWord[inputIndex] = codePoint;
-            codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos);
-        }
-    }
-    *outInputIndex = inputIndex + 1;
-    *outPos = pos;
-    return true;
-}
-
-// This function is invoked when a word like the word searched for is found.
-// It will compare the probability to the max probability, and if greater, will
-// copy the word into the output buffer. In output value maxFreq, it will
-// write the new maximum probability if it changed.
-static inline void onTerminalWordLike(const int freq, int *newWord, const int length, int *outWord,
-        int *maxFreq) {
-    if (freq > *maxFreq) {
-        for (int q = 0; q < length; ++q) {
-            outWord[q] = newWord[q];
-        }
-        outWord[length] = 0;
-        *maxFreq = freq;
-    }
-}
-
-// Will find the highest probability of the words like the one passed as an argument,
-// that is, everything that only differs by case/accents.
-int UnigramDictionary::getMostProbableWordLikeInner(const int *const inWord, const int inputSize,
-        int *outWord) const {
-    int newWord[MAX_WORD_LENGTH];
-    int depth = 0;
-    int maxFreq = -1;
-    const uint8_t *const root = DICT_ROOT;
-    int stackChildCount[MAX_WORD_LENGTH];
-    int stackInputIndex[MAX_WORD_LENGTH];
-    int stackSiblingPos[MAX_WORD_LENGTH];
-
-    int startPos = 0;
-    stackChildCount[0] = BinaryFormat::getGroupCountAndForwardPointer(root, &startPos);
-    stackInputIndex[0] = 0;
-    stackSiblingPos[0] = startPos;
-    while (depth >= 0) {
-        const int charGroupCount = stackChildCount[depth];
-        int pos = stackSiblingPos[depth];
-        for (int charGroupIndex = charGroupCount - 1; charGroupIndex >= 0; --charGroupIndex) {
-            int inputIndex = stackInputIndex[depth];
-            const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-            // Test whether all chars in this group match with the word we are searching for. If so,
-            // we want to traverse its children (or if the inputSize match, evaluate its
-            // probability). Note that this function will output the position regardless, but will
-            // only write into inputIndex if there is a match.
-            const bool isAlike = testCharGroupForContinuedLikeness(flags, root, pos, inWord,
-                    inputIndex, inputSize, newWord, &inputIndex, &pos);
-            if (isAlike && (!(BinaryFormat::FLAG_IS_NOT_A_WORD & flags))
-                    && (BinaryFormat::FLAG_IS_TERMINAL & flags) && (inputIndex == inputSize)) {
-                const int probability =
-                        BinaryFormat::readProbabilityWithoutMovingPointer(root, pos);
-                onTerminalWordLike(probability, newWord, inputIndex, outWord, &maxFreq);
-            }
-            pos = BinaryFormat::skipProbability(flags, pos);
-            const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(root, flags, pos);
-            const int childrenNodePos = BinaryFormat::readChildrenPosition(root, flags, pos);
-            // If we had a match and the word has children, we want to traverse them. We don't have
-            // to traverse words longer than the one we are searching for, since they will not match
-            // anyway, so don't traverse unless inputIndex < inputSize.
-            if (isAlike && (-1 != childrenNodePos) && (inputIndex < inputSize)) {
-                // Save position for this depth, to get back to this once children are done
-                stackChildCount[depth] = charGroupIndex;
-                stackSiblingPos[depth] = siblingPos;
-                // Prepare stack values for next depth
-                ++depth;
-                int childrenPos = childrenNodePos;
-                stackChildCount[depth] =
-                        BinaryFormat::getGroupCountAndForwardPointer(root, &childrenPos);
-                stackSiblingPos[depth] = childrenPos;
-                stackInputIndex[depth] = inputIndex;
-                pos = childrenPos;
-                // Go to the next depth level.
-                ++depth;
-                break;
-            } else {
-                // No match, or no children, or word too long to ever match: go the next sibling.
-                pos = siblingPos;
-            }
-        }
-        --depth;
-    }
-    return maxFreq;
-}
-
-int UnigramDictionary::getProbability(const int *const inWord, const int length) const {
-    const uint8_t *const root = DICT_ROOT;
-    int pos = BinaryFormat::getTerminalPosition(root, inWord, length,
-            false /* forceLowerCaseSearch */);
-    if (NOT_VALID_WORD == pos) {
-        return NOT_A_PROBABILITY;
-    }
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos);
-    if (flags & (BinaryFormat::FLAG_IS_BLACKLISTED | BinaryFormat::FLAG_IS_NOT_A_WORD)) {
-        // If this is not a word, or if it's a blacklisted entry, it should behave as
-        // having no probability outside of the suggestion process (where it should be used
-        // for shortcuts).
-        return NOT_A_PROBABILITY;
-    }
-    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
-    if (hasMultipleChars) {
-        pos = BinaryFormat::skipOtherCharacters(root, pos);
-    } else {
-        BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos);
-    }
-    const int unigramProbability = BinaryFormat::readProbabilityWithoutMovingPointer(root, pos);
-    return unigramProbability;
-}
-
-// TODO: remove this function.
-int UnigramDictionary::getBigramPosition(int pos, int *word, int offset, int length) const {
-    return -1;
-}
-
-// ProcessCurrentNode returns a boolean telling whether to traverse children nodes or not.
-// If the return value is false, then the caller should read in the output "nextSiblingPosition"
-// to find out the address of the next sibling node and pass it to a new call of processCurrentNode.
-// It is worthy to note that when false is returned, the output values other than
-// nextSiblingPosition are undefined.
-// If the return value is true, then the caller must proceed to traverse the children of this
-// node. processCurrentNode will output the information about the children: their count in
-// newCount, their position in newChildrenPosition, the traverseAllNodes flag in
-// newTraverseAllNodes, the match weight into newMatchRate, the input index into newInputIndex, the
-// diffs into newDiffs, the sibling position in nextSiblingPosition, and the output index into
-// newOutputIndex. Please also note the following caveat: processCurrentNode does not know when
-// there aren't any more nodes at this level, it merely returns the address of the first byte after
-// the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any
-// given level, as output into newCount when traversing this level's parent.
-bool UnigramDictionary::processCurrentNode(const int initialPos,
-        const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, Correction *correction,
-        int *newCount, int *newChildrenPosition, int *nextSiblingPosition,
-        WordsPriorityQueuePool *queuePool, const int currentWordIndex) const {
-    if (DEBUG_DICT) {
-        correction->checkState();
-    }
-    int pos = initialPos;
-
-    // Flags contain the following information:
-    // - Address type (MASK_GROUP_ADDRESS_TYPE) on two bits:
-    //   - FLAG_GROUP_ADDRESS_TYPE_{ONE,TWO,THREE}_BYTES means there are children and their address
-    //     is on the specified number of bytes.
-    //   - FLAG_GROUP_ADDRESS_TYPE_NOADDRESS means there are no children, and therefore no address.
-    // - FLAG_HAS_MULTIPLE_CHARS: whether this node has multiple char or not.
-    // - FLAG_IS_TERMINAL: whether this node is a terminal or not (it may still have children)
-    // - FLAG_HAS_BIGRAMS: whether this node has bigrams or not
-    const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(DICT_ROOT, &pos);
-    const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags));
-    const bool isTerminalNode = (0 != (BinaryFormat::FLAG_IS_TERMINAL & flags));
-
-    bool needsToInvokeOnTerminal = false;
-
-    // This gets only ONE character from the stream. Next there will be:
-    // if FLAG_HAS_MULTIPLE CHARS: the other characters of the same node
-    // else if FLAG_IS_TERMINAL: the probability
-    // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address
-    // Note that you can't have a node that both is not a terminal and has no children.
-    int c = BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos);
-    ASSERT(NOT_A_CODE_POINT != c);
-
-    // We are going to loop through each character and make it look like it's a different
-    // node each time. To do that, we will process characters in this node in order until
-    // we find the character terminator. This is signalled by getCodePoint* returning
-    // NOT_A_CODE_POINT.
-    // As a special case, if there is only one character in this node, we must not read the
-    // next bytes so we will simulate the NOT_A_CODE_POINT return by testing the flags.
-    // This way, each loop run will look like a "virtual node".
-    do {
-        // We prefetch the next char. If 'c' is the last char of this node, we will have
-        // NOT_A_CODE_POINT in the next char. From this we can decide whether this virtual node
-        // should behave as a terminal or not and whether we have children.
-        const int nextc = hasMultipleChars
-                ? BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CODE_POINT;
-        const bool isLastChar = (NOT_A_CODE_POINT == nextc);
-        // If there are more chars in this nodes, then this virtual node is not a terminal.
-        // If we are on the last char, this virtual node is a terminal if this node is.
-        const bool isTerminal = isLastChar && isTerminalNode;
-
-        Correction::CorrectionType stateType = correction->processCharAndCalcState(
-                c, isTerminal);
-        if (stateType == Correction::TRAVERSE_ALL_ON_TERMINAL
-                || stateType == Correction::ON_TERMINAL) {
-            needsToInvokeOnTerminal = true;
-        } else if (stateType == Correction::UNRELATED || correction->needsToPrune()) {
-            // We found that this is an unrelated character, so we should give up traversing
-            // this node and its children entirely.
-            // However we may not be on the last virtual node yet so we skip the remaining
-            // characters in this node, the probability if it's there, read the next sibling
-            // position to output it, then return false.
-            // We don't have to output other values because we return false, as in
-            // "don't traverse children".
-            if (!isLastChar) {
-                pos = BinaryFormat::skipOtherCharacters(DICT_ROOT, pos);
-            }
-            pos = BinaryFormat::skipProbability(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            return false;
-        }
-
-        // Prepare for the next character. Promote the prefetched char to current char - the loop
-        // will take care of prefetching the next. If we finally found our last char, nextc will
-        // contain NOT_A_CODE_POINT.
-        c = nextc;
-    } while (NOT_A_CODE_POINT != c);
-
-    if (isTerminalNode) {
-        // The probability should be here, because we come here only if this is actually
-        // a terminal node, and we are on its last char.
-        const int unigramProbability =
-                BinaryFormat::readProbabilityWithoutMovingPointer(DICT_ROOT, pos);
-        const int childrenAddressPos = BinaryFormat::skipProbability(flags, pos);
-        const int attributesPos = BinaryFormat::skipChildrenPosition(flags, childrenAddressPos);
-        TerminalAttributes terminalAttributes(DICT_ROOT, flags, attributesPos);
-        // bigramMap contains the bigram frequencies indexed by addresses for fast lookup.
-        // bigramFilter is a bloom filter of said frequencies for even faster rejection.
-        const int probability = BinaryFormat::getProbability(initialPos, bigramMap, bigramFilter,
-                unigramProbability);
-        onTerminal(probability, terminalAttributes, correction, queuePool, needsToInvokeOnTerminal,
-                currentWordIndex);
-
-        // If there are more chars in this node, then this virtual node has children.
-        // If we are on the last char, this virtual node has children if this node has.
-        const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
-
-        // This character matched the typed character (enough to traverse the node at least)
-        // so we just evaluated it. Now we should evaluate this virtual node's children - that
-        // is, if it has any. If it has no children, we're done here - so we skip the end of
-        // the node, output the siblings position, and return false "don't traverse children".
-        // Note that !hasChildren implies isLastChar, so we know we don't have to skip any
-        // remaining char in this group for there can't be any.
-        if (!hasChildren) {
-            pos = BinaryFormat::skipProbability(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            return false;
-        }
-
-        // Optimization: Prune out words that are too long compared to how much was typed.
-        if (correction->needsToPrune()) {
-            pos = BinaryFormat::skipProbability(flags, pos);
-            *nextSiblingPosition =
-                    BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-            if (DEBUG_DICT_FULL) {
-                AKLOGI("Traversing was pruned.");
-            }
-            return false;
-        }
-    }
-
-    // Now we finished processing this node, and we want to traverse children. If there are no
-    // children, we can't come here.
-    ASSERT(BinaryFormat::hasChildrenInFlags(flags));
-
-    // If this node was a terminal it still has the probability under the pointer (it may have been
-    // read, but not skipped - see readProbabilityWithoutMovingPointer).
-    // Next come the children position, then possibly attributes (attributes are bigrams only for
-    // now, maybe something related to shortcuts in the future).
-    // Once this is read, we still need to output the number of nodes in the immediate children of
-    // this node, so we read and output it before returning true, as in "please traverse children".
-    pos = BinaryFormat::skipProbability(flags, pos);
-    int childrenPos = BinaryFormat::readChildrenPosition(DICT_ROOT, flags, pos);
-    *nextSiblingPosition = BinaryFormat::skipChildrenPosAndAttributes(DICT_ROOT, flags, pos);
-    *newCount = BinaryFormat::getGroupCountAndForwardPointer(DICT_ROOT, &childrenPos);
-    *newChildrenPosition = childrenPos;
-    return true;
-}
-} // namespace latinime
diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h
deleted file mode 100644
index a64a539..0000000
--- a/native/jni/src/unigram_dictionary.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_UNIGRAM_DICTIONARY_H
-#define LATINIME_UNIGRAM_DICTIONARY_H
-
-#include <map>
-#include <stdint.h>
-#include "defines.h"
-#include "digraph_utils.h"
-
-namespace latinime {
-
-class Correction;
-class ProximityInfo;
-class TerminalAttributes;
-class WordsPriorityQueuePool;
-
-class UnigramDictionary {
- public:
-    // Error tolerances
-    static const int DEFAULT_MAX_ERRORS = 2;
-    static const int MAX_ERRORS_FOR_TWO_WORDS = 1;
-
-    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 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,
-            const int *ycoordinates, const int *inputCodePoints, const int inputSize,
-            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:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(UnigramDictionary);
-    void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *inputCodePoints, const int inputSize,
-            const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-            const bool useFullEditDistance, Correction *correction,
-            WordsPriorityQueuePool *queuePool) const;
-    int getDigraphReplacement(const int *codes, const int i, const int inputSize,
-            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 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,
-            Correction *correction) const;
-    void getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap,
-            const uint8_t *bigramFilter, const bool useFullEditDistance, const int inputSize,
-            Correction *correction, WordsPriorityQueuePool *queuePool) const;
-    void getSuggestionCandidates(
-            const bool useFullEditDistance, const int inputSize,
-            const std::map<int, int> *bigramMap, const uint8_t *bigramFilter,
-            Correction *correction, WordsPriorityQueuePool *queuePool, const bool doAutoCompletion,
-            const int maxErrors, const int currentWordIndex) const;
-    void getSplitMultipleWordsSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
-            const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool,
-            const bool hasAutoCorrectionCandidate) const;
-    void onTerminal(const int freq, const TerminalAttributes &terminalAttributes,
-            Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue,
-            const int currentWordIndex) const;
-    // Process a node by considering proximity, missing and excessive character
-    bool processCurrentNode(const int initialPos, const std::map<int, int> *bigramMap,
-            const uint8_t *bigramFilter, Correction *correction, int *newCount,
-            int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool,
-            const int currentWordIndex) const;
-    int getMostProbableWordLike(const int startInputIndex, const int inputSize,
-            Correction *correction, int *word) const;
-    int getMostProbableWordLikeInner(const int *const inWord, const int inputSize,
-            int *outWord) const;
-    int getSubStringSuggestion(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
-            Correction *correction, WordsPriorityQueuePool *queuePool, const int inputSize,
-            const bool hasAutoCorrectionCandidate, const int currentWordIndex,
-            const int inputWordStartPos, const int inputWordLength, const int outputWordStartPos,
-            const bool isSpaceProximity, int *freqArray, int *wordLengthArray, int *outputWord,
-            int *outputWordLength) const;
-    void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, const int *xcoordinates,
-            const int *ycoordinates, const int *codes, const bool useFullEditDistance,
-            const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool,
-            const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex,
-            const int outputWordLength, int *freqArray, int *wordLengthArray,
-            int *outputWord) const;
-
-    const uint8_t *const DICT_ROOT;
-    const int ROOT_POS;
-    const int MAX_DIGRAPH_SEARCH_DEPTH;
-    const int DICT_FLAGS;
-};
-} // namespace latinime
-#endif // LATINIME_UNIGRAM_DICTIONARY_H
diff --git a/native/jni/src/utils/autocorrection_threshold_utils.cpp b/native/jni/src/utils/autocorrection_threshold_utils.cpp
new file mode 100644
index 0000000..3406e0f
--- /dev/null
+++ b/native/jni/src/utils/autocorrection_threshold_utils.cpp
@@ -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.
+ */
+
+#include "utils/autocorrection_threshold_utils.h"
+
+#include <cmath>
+
+#include "defines.h"
+#include "suggest/policyimpl/utils/edit_distance.h"
+#include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h"
+
+namespace latinime {
+
+const int AutocorrectionThresholdUtils::MAX_INITIAL_SCORE = 255;
+const int AutocorrectionThresholdUtils::TYPED_LETTER_MULTIPLIER = 2;
+const int AutocorrectionThresholdUtils::FULL_WORD_MULTIPLIER = 2;
+
+/* static */ int AutocorrectionThresholdUtils::editDistance(const int *before,
+        const int beforeLength, const int *after, const int afterLength) {
+    const DamerauLevenshteinEditDistancePolicy daemaruLevenshtein(
+            before, beforeLength, after, afterLength);
+    return static_cast<int>(EditDistance::getEditDistance(&daemaruLevenshtein));
+}
+
+// In dictionary.cpp, getSuggestion() method,
+// When USE_SUGGEST_INTERFACE_FOR_TYPING is true:
+//
+//   // TODO: Revise the following logic thoroughly by referring to the logic
+//   // marked as "Otherwise" below.
+//   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))
+//     * (individual word's score which defined in the unigram dictionary,
+//         and this score is defined in range [0, 255].)
+// Then, the following processing is applied.
+//     - If the dictionary word is matched up to the point of the user entry
+//       (full match up to min(before.length(), after.length())
+//       => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
+//     - If the word is a true full match except for differences in accents or
+//       capitalization, then treat it as if the score was 255.
+//     - If before.length() == after.length()
+//       => multiply by mFullWordMultiplier (this is defined 2))
+// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+// For historical reasons we ignore the 1.2 modifier (because the measure for a good
+// autocorrection threshold was done at a time when it didn't exist). This doesn't change
+// the result.
+// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2.
+
+/* static */ float AutocorrectionThresholdUtils::calcNormalizedScore(const int *before,
+        const int beforeLength, const int *after, const int afterLength, const int score) {
+    if (0 == beforeLength || 0 == afterLength) {
+        return 0.0f;
+    }
+    const int distance = editDistance(before, beforeLength, after, afterLength);
+    int spaceCount = 0;
+    for (int i = 0; i < afterLength; ++i) {
+        if (after[i] == KEYCODE_SPACE) {
+            ++spaceCount;
+        }
+    }
+
+    if (spaceCount == afterLength) {
+        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);
+
+    // TODO: Revise the following logic thoroughly by referring to...
+    if (true /* USE_SUGGEST_INTERFACE_FOR_TYPING */) {
+        return (static_cast<float>(score) / SUGGEST_INTERFACE_OUTPUT_SCALE) * weight;
+    }
+    // ...this logic.
+    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);
+
+    return (static_cast<float>(score) / maxScore) * weight;
+}
+
+} // namespace latinime
diff --git a/native/jni/src/utils/autocorrection_threshold_utils.h b/native/jni/src/utils/autocorrection_threshold_utils.h
new file mode 100644
index 0000000..c7537a6
--- /dev/null
+++ b/native/jni/src/utils/autocorrection_threshold_utils.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_AUTOCORRECTION_THRESHOLD_UTILS_H
+#define LATINIME_AUTOCORRECTION_THRESHOLD_UTILS_H
+
+#include "defines.h"
+
+namespace latinime {
+
+class AutocorrectionThresholdUtils {
+ public:
+    static float calcNormalizedScore(const int *before, const int beforeLength,
+            const int *after, const int afterLength, const int score);
+    static int editDistance(const int *before, const int beforeLength, const int *after,
+            const int afterLength);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(AutocorrectionThresholdUtils);
+
+    static const int MAX_INITIAL_SCORE;
+    static const int TYPED_LETTER_MULTIPLIER;
+    static const int FULL_WORD_MULTIPLIER;
+};
+} // namespace latinime
+#endif // LATINIME_AUTOCORRECTION_THRESHOLD_UTILS_H
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/utils/char_utils.cpp
similarity index 99%
rename from native/jni/src/char_utils.cpp
rename to native/jni/src/utils/char_utils.cpp
index e219beb..0e70396 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/utils/char_utils.cpp
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
+#include "utils/char_utils.h"
+
 #include <cstdlib>
 
-#include "char_utils.h"
 #include "defines.h"
 
 namespace latinime {
@@ -36,8 +37,7 @@
  *    $ apt-get install libicu-dev
  *
  * 3. Build the following code
- *    (You need this file, char_utils.h, and defines.h)
- *    $ g++ -o char_utils -DUPDATING_CHAR_UTILS char_utils.cpp -licuuc
+ *    $ g++ -o char_utils -I.. -DUPDATING_CHAR_UTILS char_utils.cpp -licuuc
  */
 #ifdef UPDATING_CHAR_UTILS
 #include <stdio.h>
@@ -47,7 +47,7 @@
     for (unsigned short c = 0; c < 0xFFFF; c++) {
         if (c <= 0x7F) continue;
         const unsigned short icu4cLowerC = u_tolower(c);
-        const unsigned short myLowerC = latin_tolower(c);
+        const unsigned short myLowerC = CharUtils::latin_tolower(c);
         if (c != icu4cLowerC) {
 #ifdef CONFIRMING_CHAR_UTILS
             if (icu4cLowerC != myLowerC) {
@@ -70,7 +70,7 @@
  *
  * 5. Update the SORTED_CHAR_MAP[] array below with the output above.
  *    Then, rebuild with -DCONFIRMING_CHAR_UTILS and confirm the program exits successfully.
- *    $ g++ -o char_utils -DUPDATING_CHAR_UTILS -DCONFIRMING_CHAR_UTILS char_utils.cpp -licuuc
+ *    $ g++ -o char_utils -I.. -DUPDATING_CHAR_UTILS -DCONFIRMING_CHAR_UTILS char_utils.cpp -licuuc
  *    $ ./char_utils
  *    $
  */
@@ -1054,7 +1054,7 @@
             - static_cast<int>((static_cast<const struct LatinCapitalSmallPair *>(b))->capital);
 }
 
-unsigned short latin_tolower(const unsigned short c) {
+/* static */ unsigned short CharUtils::latin_tolower(const unsigned short c) {
     struct LatinCapitalSmallPair *p =
             static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP,
                     NELEMS(SORTED_CHAR_MAP), sizeof(SORTED_CHAR_MAP[0]), compare_pair_capital));
@@ -1063,7 +1063,7 @@
 
 /*
  * Table mapping most combined Latin, Greek, and Cyrillic characters
- * to their base characters.  If c is in range, BASE_CHARS[c] == c
+ * to their base characters.  If c is in range, CharUtils::BASE_CHARS[c] == c
  * if c is not a combined character, or the base character if it
  * is combined.
  *
@@ -1074,7 +1074,7 @@
  *   for ($j = $i; $j < $i + 8; $j++) { \
  *   printf("0x%04X, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
  */
-const unsigned short BASE_CHARS[BASE_CHARS_SIZE] = {
+/* static */ const unsigned short CharUtils::BASE_CHARS[CharUtils::BASE_CHARS_SIZE] = {
     /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
     /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
     /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
diff --git a/native/jni/src/utils/char_utils.h b/native/jni/src/utils/char_utils.h
new file mode 100644
index 0000000..2e735a8
--- /dev/null
+++ b/native/jni/src/utils/char_utils.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_CHAR_UTILS_H
+#define LATINIME_CHAR_UTILS_H
+
+#include <cctype>
+
+#include "defines.h"
+
+namespace latinime {
+
+class CharUtils {
+ public:
+    static AK_FORCE_INLINE bool isAsciiUpper(int c) {
+        // Note: isupper(...) reports false positives for some Cyrillic characters, causing them to
+        // be incorrectly lower-cased using toAsciiLower(...) rather than latin_tolower(...).
+        return (c >= 'A' && c <= 'Z');
+    }
+
+    static AK_FORCE_INLINE int toAsciiLower(int c) {
+        return c - 'A' + 'a';
+    }
+
+    static AK_FORCE_INLINE bool isAscii(int c) {
+        return isascii(c) != 0;
+    }
+
+    static AK_FORCE_INLINE int toLowerCase(const int c) {
+        if (isAsciiUpper(c)) {
+            return toAsciiLower(c);
+        }
+        if (isAscii(c)) {
+            return c;
+        }
+        return static_cast<int>(latin_tolower(static_cast<unsigned short>(c)));
+    }
+
+    static AK_FORCE_INLINE int toBaseLowerCase(const int c) {
+        return toLowerCase(toBaseCodePoint(c));
+    }
+
+    static AK_FORCE_INLINE bool isIntentionalOmissionCodePoint(const int codePoint) {
+        // TODO: Do not hardcode here
+        return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS;
+    }
+
+    static AK_FORCE_INLINE int getCodePointCount(const int arraySize, const int *const codePoints) {
+        int size = 0;
+        for (; size < arraySize; ++size) {
+            if (codePoints[size] == '\0') {
+                break;
+            }
+        }
+        return size;
+    }
+
+    static AK_FORCE_INLINE int toBaseCodePoint(int c) {
+        if (c < BASE_CHARS_SIZE) {
+            return static_cast<int>(BASE_CHARS[c]);
+        }
+        return c;
+    }
+
+    static unsigned short latin_tolower(const unsigned short c);
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(CharUtils);
+
+    /**
+     * Table mapping most combined Latin, Greek, and Cyrillic characters
+     * to their base characters.  If c is in range, BASE_CHARS[c] == c
+     * if c is not a combined character, or the base character if it
+     * is combined.
+     */
+    static const int BASE_CHARS_SIZE = 0x0500;
+    static const unsigned short BASE_CHARS[BASE_CHARS_SIZE];
+};
+} // namespace latinime
+#endif // LATINIME_CHAR_UTILS_H
diff --git a/native/jni/src/hash_map_compat.h b/native/jni/src/utils/hash_map_compat.h
similarity index 100%
rename from native/jni/src/hash_map_compat.h
rename to native/jni/src/utils/hash_map_compat.h
diff --git a/native/jni/src/utils/log_utils.cpp b/native/jni/src/utils/log_utils.cpp
new file mode 100644
index 0000000..5ab2b28
--- /dev/null
+++ b/native/jni/src/utils/log_utils.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log_utils.h"
+
+#include <cstdio>
+#include <stdarg.h>
+
+#include "defines.h"
+
+namespace latinime {
+    /* static */ void LogUtils::logToJava(JNIEnv *const env, const char *const format, ...) {
+        static const char *TAG = "LatinIME:LogUtils";
+        const jclass androidUtilLogClass = env->FindClass("android/util/Log");
+        if (!androidUtilLogClass) {
+            // If we can't find the class, we are probably in off-device testing, and
+            // it's expected. Regardless, logging is not essential to functionality, so
+            // we should just return. However, FindClass has thrown an exception behind
+            // our back and there is no way to prevent it from doing that, so we clear
+            // the exception before we return.
+            env->ExceptionClear();
+            return;
+        }
+        const jmethodID logDotIMethodId = env->GetStaticMethodID(androidUtilLogClass, "i",
+                "(Ljava/lang/String;Ljava/lang/String;)I");
+        if (!logDotIMethodId) {
+            env->ExceptionClear();
+            if (androidUtilLogClass) env->DeleteLocalRef(androidUtilLogClass);
+            return;
+        }
+        const jstring javaTag = env->NewStringUTF(TAG);
+
+        static const int DEFAULT_LINE_SIZE = 128;
+        char fixedSizeCString[DEFAULT_LINE_SIZE];
+        va_list argList;
+        va_start(argList, format);
+        // Get the necessary size. Add 1 for the 0 terminator.
+        const int size = vsnprintf(fixedSizeCString, DEFAULT_LINE_SIZE, format, argList) + 1;
+        va_end(argList);
+
+        jstring javaString;
+        if (size <= DEFAULT_LINE_SIZE) {
+            // The buffer was large enough.
+            javaString = env->NewStringUTF(fixedSizeCString);
+        } else {
+            // The buffer was not large enough.
+            va_start(argList, format);
+            char variableSizeCString[size];
+            vsnprintf(variableSizeCString, size, format, argList);
+            va_end(argList);
+            javaString = env->NewStringUTF(variableSizeCString);
+        }
+
+        env->CallStaticIntMethod(androidUtilLogClass, logDotIMethodId, javaTag, javaString);
+        if (javaString) env->DeleteLocalRef(javaString);
+        if (javaTag) env->DeleteLocalRef(javaTag);
+        if (androidUtilLogClass) env->DeleteLocalRef(androidUtilLogClass);
+    }
+}
diff --git a/native/jni/src/utils/log_utils.h b/native/jni/src/utils/log_utils.h
new file mode 100644
index 0000000..6ac16d9
--- /dev/null
+++ b/native/jni/src/utils/log_utils.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_LOG_UTILS_H
+#define LATINIME_LOG_UTILS_H
+
+#include "defines.h"
+#include "jni.h"
+
+namespace latinime {
+
+class LogUtils {
+ public:
+    static void logToJava(JNIEnv *const env, const char *const format, ...)
+#ifdef __GNUC__
+        __attribute__ ((format (printf, 2, 3)))
+#endif // __GNUC__
+        ;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(LogUtils);
+};
+} // namespace latinime
+#endif // LATINIME_LOG_UTILS_H
diff --git a/native/jni/src/words_priority_queue.cpp b/native/jni/src/words_priority_queue.cpp
deleted file mode 100644
index 7e18d0f..0000000
--- a/native/jni/src/words_priority_queue.cpp
+++ /dev/null
@@ -1,76 +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 "words_priority_queue.h"
-
-namespace latinime {
-
-int WordsPriorityQueue::outputSuggestions(const int *before, const int beforeLength,
-        int *frequencies, int *outputCodePoints, int* outputTypes) {
-    mHighestSuggestedWord = 0;
-    const int size = min(MAX_WORDS, static_cast<int>(mSuggestions.size()));
-    SuggestedWord *swBuffer[size];
-    int index = size - 1;
-    while (!mSuggestions.empty() && index >= 0) {
-        SuggestedWord *sw = mSuggestions.top();
-        if (DEBUG_WORDS_PRIORITY_QUEUE) {
-            AKLOGI("dump word. %d", sw->mScore);
-            DUMP_WORD(sw->mWord, sw->mWordLength);
-        }
-        swBuffer[index] = sw;
-        mSuggestions.pop();
-        --index;
-    }
-    if (size >= 2) {
-        SuggestedWord *nsMaxSw = 0;
-        int maxIndex = 0;
-        float maxNs = 0;
-        for (int i = 0; i < size; ++i) {
-            SuggestedWord *tempSw = swBuffer[i];
-            if (!tempSw) {
-                continue;
-            }
-            const float tempNs = getNormalizedScore(tempSw, before, beforeLength, 0, 0, 0);
-            if (tempNs >= maxNs) {
-                maxNs = tempNs;
-                maxIndex = i;
-                nsMaxSw = tempSw;
-            }
-        }
-        if (maxIndex > 0 && nsMaxSw) {
-            memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(swBuffer[0]));
-            swBuffer[0] = nsMaxSw;
-        }
-    }
-    for (int i = 0; i < size; ++i) {
-        SuggestedWord *sw = swBuffer[i];
-        if (!sw) {
-            AKLOGE("SuggestedWord is null %d", i);
-            continue;
-        }
-        const int wordLength = sw->mWordLength;
-        int *targetAddress = outputCodePoints + i * MAX_WORD_LENGTH;
-        frequencies[i] = sw->mScore;
-        outputTypes[i] = sw->mType;
-        memcpy(targetAddress, sw->mWord, wordLength * sizeof(targetAddress[0]));
-        if (wordLength < MAX_WORD_LENGTH) {
-            targetAddress[wordLength] = 0;
-        }
-        sw->mUsed = false;
-    }
-    return size;
-}
-} // namespace latinime
diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h
deleted file mode 100644
index 54e8007..0000000
--- a/native/jni/src/words_priority_queue.h
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_WORDS_PRIORITY_QUEUE_H
-#define LATINIME_WORDS_PRIORITY_QUEUE_H
-
-#include <cstring> // for memcpy()
-#include <queue>
-
-#include "correction.h"
-#include "defines.h"
-
-namespace latinime {
-
-class WordsPriorityQueue {
- public:
-    struct SuggestedWord {
-        int mScore;
-        int mWord[MAX_WORD_LENGTH];
-        int mWordLength;
-        bool mUsed;
-        int mType;
-
-        void setParams(int score, int *word, int wordLength, int type) {
-            mScore = score;
-            mWordLength = wordLength;
-            memcpy(mWord, word, sizeof(mWord[0]) * wordLength);
-            mUsed = true;
-            mType = type;
-        }
-    };
-
-    WordsPriorityQueue(int maxWords)
-            : mSuggestions(), MAX_WORDS(maxWords),
-              mSuggestedWords(new SuggestedWord[MAX_WORD_LENGTH]), mHighestSuggestedWord(0) {
-        for (int i = 0; i < MAX_WORD_LENGTH; ++i) {
-            mSuggestedWords[i].mUsed = false;
-        }
-    }
-
-    // Non virtual inline destructor -- never inherit this class
-    AK_FORCE_INLINE ~WordsPriorityQueue() {
-        delete[] mSuggestedWords;
-    }
-
-    void push(int score, int *word, int wordLength, int type) {
-        SuggestedWord *sw = 0;
-        if (size() >= MAX_WORDS) {
-            sw = mSuggestions.top();
-            const int minScore = sw->mScore;
-            if (minScore >= score) {
-                return;
-            }
-            sw->mUsed = false;
-            mSuggestions.pop();
-        }
-        if (sw == 0) {
-            sw = getFreeSuggestedWord(score, word, wordLength, type);
-        } else {
-            sw->setParams(score, word, wordLength, type);
-        }
-        if (sw == 0) {
-            AKLOGE("SuggestedWord is accidentally null.");
-            return;
-        }
-        if (DEBUG_WORDS_PRIORITY_QUEUE) {
-            AKLOGI("Push word. %d, %d", score, wordLength);
-            DUMP_WORD(word, wordLength);
-        }
-        mSuggestions.push(sw);
-        if (!mHighestSuggestedWord || mHighestSuggestedWord->mScore < sw->mScore) {
-            mHighestSuggestedWord = sw;
-        }
-    }
-
-    SuggestedWord *top() const {
-        if (mSuggestions.empty()) return 0;
-        SuggestedWord *sw = mSuggestions.top();
-        return sw;
-    }
-
-    int size() const {
-        return static_cast<int>(mSuggestions.size());
-    }
-
-    AK_FORCE_INLINE void clear() {
-        mHighestSuggestedWord = 0;
-        while (!mSuggestions.empty()) {
-            SuggestedWord *sw = mSuggestions.top();
-            if (DEBUG_WORDS_PRIORITY_QUEUE) {
-                AKLOGI("Clear word. %d", sw->mScore);
-                DUMP_WORD(sw->mWord, sw->mWordLength);
-            }
-            sw->mUsed = false;
-            mSuggestions.pop();
-        }
-    }
-
-    AK_FORCE_INLINE void dumpTopWord() const {
-        if (size() <= 0) {
-            return;
-        }
-        DUMP_WORD(mHighestSuggestedWord->mWord, mHighestSuggestedWord->mWordLength);
-    }
-
-    AK_FORCE_INLINE float getHighestNormalizedScore(const int *before, const int beforeLength,
-            int **outWord, int *outScore, int *outLength) const {
-        if (!mHighestSuggestedWord) {
-            return 0.0f;
-        }
-        return getNormalizedScore(mHighestSuggestedWord, before, beforeLength, outWord, outScore,
-                outLength);
-    }
-
-    int outputSuggestions(const int *before, const int beforeLength, int *frequencies,
-            int *outputCodePoints, int* outputTypes);
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueue);
-    struct wordComparator {
-        bool operator ()(SuggestedWord * left, SuggestedWord * right) {
-            return left->mScore > right->mScore;
-        }
-    };
-
-    SuggestedWord *getFreeSuggestedWord(int score, int *word, int wordLength, int type) const {
-        for (int i = 0; i < MAX_WORD_LENGTH; ++i) {
-            if (!mSuggestedWords[i].mUsed) {
-                mSuggestedWords[i].setParams(score, word, wordLength, type);
-                return &mSuggestedWords[i];
-            }
-        }
-        return 0;
-    }
-
-    static float getNormalizedScore(SuggestedWord *sw, const int *before, const int beforeLength,
-            int **outWord, int *outScore, int *outLength) {
-        const int score = sw->mScore;
-        int *word = sw->mWord;
-        const int wordLength = sw->mWordLength;
-        if (outScore) {
-            *outScore = score;
-        }
-        if (outWord) {
-            *outWord = word;
-        }
-        if (outLength) {
-            *outLength = wordLength;
-        }
-        return Correction::RankingAlgorithm::calcNormalizedScore(before, beforeLength, word,
-                wordLength, score);
-    }
-
-    typedef std::priority_queue<SuggestedWord *, std::vector<SuggestedWord *>,
-            wordComparator> Suggestions;
-    Suggestions mSuggestions;
-    const int MAX_WORDS;
-    SuggestedWord *mSuggestedWords;
-    SuggestedWord *mHighestSuggestedWord;
-};
-} // namespace latinime
-#endif // LATINIME_WORDS_PRIORITY_QUEUE_H
diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h
deleted file mode 100644
index 2cd210a..0000000
--- a/native/jni/src/words_priority_queue_pool.h
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
-#define LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
-
-#include "defines.h"
-#include "words_priority_queue.h"
-
-namespace latinime {
-
-class WordsPriorityQueuePool {
- public:
-    WordsPriorityQueuePool(int mainQueueMaxWords, int subQueueMaxWords)
-            // Note: using placement new() requires the caller to call the destructor explicitly.
-            : mMasterQueue(new(mMasterQueueBuf) WordsPriorityQueue(mainQueueMaxWords)) {
-        for (int i = 0, subQueueBufOffset = 0;
-                i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT;
-                ++i, subQueueBufOffset += static_cast<int>(sizeof(WordsPriorityQueue))) {
-            mSubQueues[i] = new(mSubQueueBuf + subQueueBufOffset)
-                    WordsPriorityQueue(subQueueMaxWords);
-        }
-    }
-
-    // Non virtual inline destructor -- never inherit this class
-    ~WordsPriorityQueuePool() {
-        // Note: these explicit calls to the destructor match the calls to placement new() above.
-        if (mMasterQueue) mMasterQueue->~WordsPriorityQueue();
-        for (int i = 0; i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT; ++i) {
-            if (mSubQueues[i]) mSubQueues[i]->~WordsPriorityQueue();
-        }
-    }
-
-    WordsPriorityQueue *getMasterQueue() const {
-        return mMasterQueue;
-    }
-
-    WordsPriorityQueue *getSubQueue(const int wordIndex, const int inputWordLength) const {
-        if (wordIndex >= MULTIPLE_WORDS_SUGGESTION_MAX_WORDS) {
-            return 0;
-        }
-        if (inputWordLength < 0 || inputWordLength >= SUB_QUEUE_MAX_COUNT) {
-            if (DEBUG_WORDS_PRIORITY_QUEUE) {
-                ASSERT(false);
-            }
-            return 0;
-        }
-        return mSubQueues[wordIndex * SUB_QUEUE_MAX_COUNT + inputWordLength];
-    }
-
-    inline void clearAll() {
-        mMasterQueue->clear();
-        for (int i = 0; i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS; ++i) {
-            clearSubQueue(i);
-        }
-    }
-
-    AK_FORCE_INLINE void clearSubQueue(const int wordIndex) {
-        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
-            WordsPriorityQueue *queue = getSubQueue(wordIndex, i);
-            if (queue) {
-                queue->clear();
-            }
-        }
-    }
-
-    void dumpSubQueue1TopSuggestions() const {
-        AKLOGI("DUMP SUBQUEUE1 TOP SUGGESTIONS");
-        for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) {
-            getSubQueue(0, i)->dumpTopWord();
-        }
-    }
-
- private:
-    DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueuePool);
-    char mMasterQueueBuf[sizeof(WordsPriorityQueue)];
-    char mSubQueueBuf[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS
-            * sizeof(WordsPriorityQueue)];
-    WordsPriorityQueue *mMasterQueue;
-    WordsPriorityQueue *mSubQueues[SUB_QUEUE_MAX_COUNT * MULTIPLE_WORDS_SUGGESTION_MAX_WORDS];
-};
-} // namespace latinime
-#endif // LATINIME_WORDS_PRIORITY_QUEUE_POOL_H
diff --git a/tests/res/raw/dummy_resource_for_testing.txt b/tests/res/raw/dummy_resource_for_testing.txt
new file mode 100644
index 0000000..05da86b
--- /dev/null
+++ b/tests/res/raw/dummy_resource_for_testing.txt
@@ -0,0 +1,3 @@
+/* This dummy raw resource is needed to be able to load string resources from a test APK
+ * successfully. (See {@link KeySpecParserSplitTests#setUp()}.
+ */
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
index 01814ae..6d9c3fd 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderFixedOrderTests.java
@@ -47,7 +47,7 @@
             final int coordXInParent) {
         final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
         params.setParameters(numKeys, columnNum, WIDTH, HEIGHT, coordXInParent, KEYBOARD_WIDTH,
-                /* isFixedOrderColumn */true, /* dividerWidth */0);
+                true /* isFixedOrderColumn */, 0 /* dividerWidth */);
         return params;
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
index ce5573d..b213721 100644
--- a/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/MoreKeysKeyboardBuilderTests.java
@@ -47,7 +47,7 @@
             final int coordXInParent) {
         final MoreKeysKeyboardParams params = new MoreKeysKeyboardParams();
         params.setParameters(numKeys, maxColumns, WIDTH, HEIGHT, coordXInParent, KEYBOARD_WIDTH,
-                /* isFixedOrderColumn */false, /* dividerWidth */0);
+                false /* isFixedOrderColumn */, 0 /* dividerWidth */);
         return params;
     }
 
diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
deleted file mode 100644
index 850af94..0000000
--- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java
+++ /dev/null
@@ -1,216 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.keyboard;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.AdditionalSubtype;
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.RichInputMethodManager;
-import com.android.inputmethod.latin.StringUtils;
-import com.android.inputmethod.latin.SubtypeLocale;
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-@SmallTest
-public class SpacebarTextTests extends AndroidTestCase {
-    // Locale to subtypes list.
-    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
-
-    private RichInputMethodManager mRichImm;
-    private Resources mRes;
-
-    InputMethodSubtype EN_US;
-    InputMethodSubtype EN_GB;
-    InputMethodSubtype ES_US;
-    InputMethodSubtype FR;
-    InputMethodSubtype FR_CA;
-    InputMethodSubtype DE;
-    InputMethodSubtype ZZ;
-    InputMethodSubtype DE_QWERTY;
-    InputMethodSubtype FR_QWERTZ;
-    InputMethodSubtype US_AZERTY;
-    InputMethodSubtype ZZ_AZERTY;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        final Context context = getContext();
-        RichInputMethodManager.init(context);
-        mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocale.init(context);
-
-        EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(Locale.US.toString(), "qwerty");
-        EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(Locale.UK.toString(), "qwerty");
-        ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet("es_US", "spanish");
-        FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(Locale.FRENCH.toString(), "azerty");
-        FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.CANADA_FRENCH.toString(), "qwerty");
-        DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(Locale.GERMAN.toString(), "qwertz");
-        ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(SubtypeLocale.NO_LANGUAGE, "qwerty");
-        DE_QWERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.GERMAN.toString(), "qwerty", null);
-        FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype(
-                Locale.FRENCH.toString(), "qwertz", null);
-        US_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.US.toString(), "azerty", null);
-        ZZ_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                SubtypeLocale.NO_LANGUAGE, "azerty", null);
-    }
-
-    public void testAllFullDisplayName() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocale.getSubtypeDisplayName(subtype);
-            final String spacebarText = MainKeyboardView.getFullDisplayName(subtype);
-            final String languageName =
-                    SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale());
-            if (SubtypeLocale.isNoLanguage(subtype)) {
-                assertFalse(subtypeName, spacebarText.contains(languageName));
-            } else {
-                assertTrue(subtypeName, spacebarText.contains(languageName));
-            }
-        }
-    }
-
-   public void testAllMiddleDisplayName() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocale.getSubtypeDisplayName(subtype);
-            final String spacebarText = MainKeyboardView.getMiddleDisplayName(subtype);
-            if (SubtypeLocale.isNoLanguage(subtype)) {
-                assertEquals(subtypeName,
-                        SubtypeLocale.getKeyboardLayoutSetName(subtype), spacebarText);
-            } else {
-                assertEquals(subtypeName,
-                        SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale()),
-                        spacebarText);
-            }
-        }
-    }
-
-    public void testAllShortDisplayName() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocale.getSubtypeDisplayName(subtype);
-            final Locale locale = SubtypeLocale.getSubtypeLocale(subtype);
-            final String spacebarText = MainKeyboardView.getShortDisplayName(subtype);
-            final String languageCode = StringUtils.capitalizeFirstCodePoint(
-                    locale.getLanguage(), locale);
-            if (SubtypeLocale.isNoLanguage(subtype)) {
-                assertEquals(subtypeName, "", spacebarText);
-            } else {
-                assertEquals(subtypeName, languageCode, spacebarText);
-            }
-        }
-    }
-
-    // InputMethodSubtype's display name for spacebar text in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  | Short  Middle      Full
-    // ------ ------- - ---- --------- ----------------------
-    //  en_US qwerty  F  En  English   English (US)           exception
-    //  en_GB qwerty  F  En  English   English (UK)           exception
-    //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
-    //  fr    azerty  F  Fr  Français  Français
-    //  fr_CA qwerty  F  Fr  Français  Français (Canada)
-    //  de    qwertz  F  De  Deutsch   Deutsch
-    //  zz    qwerty  F      QWERTY    QWERTY
-    //  fr    qwertz  T  Fr  Français  Français
-    //  de    qwerty  T  De  Deutsch   Deutsch
-    //  en_US azerty  T  En  English   English (US)
-    //  zz    azerty  T      AZERTY    AZERTY
-
-    private final RunInLocale<Void> testsPredefinedSubtypes = new RunInLocale<Void>() {
-        @Override
-        protected Void job(Resources res) {
-            assertEquals("en_US", "English (US)",      MainKeyboardView.getFullDisplayName(EN_US));
-            assertEquals("en_GB", "English (UK)",      MainKeyboardView.getFullDisplayName(EN_GB));
-            assertEquals("es_US", "Español (EE.UU.)",  MainKeyboardView.getFullDisplayName(ES_US));
-            assertEquals("fr   ", "Français",          MainKeyboardView.getFullDisplayName(FR));
-            assertEquals("fr_CA", "Français (Canada)", MainKeyboardView.getFullDisplayName(FR_CA));
-            assertEquals("de   ", "Deutsch",           MainKeyboardView.getFullDisplayName(DE));
-            assertEquals("zz   ", "QWERTY",            MainKeyboardView.getFullDisplayName(ZZ));
-
-            assertEquals("en_US", "English",  MainKeyboardView.getMiddleDisplayName(EN_US));
-            assertEquals("en_GB", "English",  MainKeyboardView.getMiddleDisplayName(EN_GB));
-            assertEquals("es_US", "Español",  MainKeyboardView.getMiddleDisplayName(ES_US));
-            assertEquals("fr   ", "Français", MainKeyboardView.getMiddleDisplayName(FR));
-            assertEquals("fr_CA", "Français", MainKeyboardView.getMiddleDisplayName(FR_CA));
-            assertEquals("de   ", "Deutsch",  MainKeyboardView.getMiddleDisplayName(DE));
-            assertEquals("zz   ", "QWERTY",   MainKeyboardView.getMiddleDisplayName(ZZ));
-
-            assertEquals("en_US", "En", MainKeyboardView.getShortDisplayName(EN_US));
-            assertEquals("en_GB", "En", MainKeyboardView.getShortDisplayName(EN_GB));
-            assertEquals("es_US", "Es", MainKeyboardView.getShortDisplayName(ES_US));
-            assertEquals("fr   ", "Fr", MainKeyboardView.getShortDisplayName(FR));
-            assertEquals("fr_CA", "Fr", MainKeyboardView.getShortDisplayName(FR_CA));
-            assertEquals("de   ", "De", MainKeyboardView.getShortDisplayName(DE));
-            assertEquals("zz   ", "",   MainKeyboardView.getShortDisplayName(ZZ));
-            return null;
-        }
-    };
-
-    private final RunInLocale<Void> testsAdditionalSubtypes = new RunInLocale<Void>() {
-        @Override
-        protected Void job(Resources res) {
-            assertEquals("fr qwertz",    "Français",
-                    MainKeyboardView.getFullDisplayName(FR_QWERTZ));
-            assertEquals("de qwerty",    "Deutsch",
-                    MainKeyboardView.getFullDisplayName(DE_QWERTY));
-            assertEquals("en_US azerty", "English (US)",
-                    MainKeyboardView.getFullDisplayName(US_AZERTY));
-            assertEquals("zz azerty",    "AZERTY",
-                    MainKeyboardView.getFullDisplayName(ZZ_AZERTY));
-
-            assertEquals("fr qwertz",    "Français",
-                    MainKeyboardView.getMiddleDisplayName(FR_QWERTZ));
-            assertEquals("de qwerty",    "Deutsch",
-                    MainKeyboardView.getMiddleDisplayName(DE_QWERTY));
-            assertEquals("en_US azerty", "English",
-                    MainKeyboardView.getMiddleDisplayName(US_AZERTY));
-            assertEquals("zz azerty",    "AZERTY",
-                    MainKeyboardView.getMiddleDisplayName(ZZ_AZERTY));
-
-            assertEquals("fr qwertz",    "Fr", MainKeyboardView.getShortDisplayName(FR_QWERTZ));
-            assertEquals("de qwerty",    "De", MainKeyboardView.getShortDisplayName(DE_QWERTY));
-            assertEquals("en_US azerty", "En", MainKeyboardView.getShortDisplayName(US_AZERTY));
-            assertEquals("zz azerty",    "",   MainKeyboardView.getShortDisplayName(ZZ_AZERTY));
-            return null;
-        }
-    };
-
-    public void testPredefinedSubtypesInEnglish() {
-        testsPredefinedSubtypes.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testAdditionalSubtypeInEnglish() {
-        testsAdditionalSubtypes.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testPredefinedSubtypesInFrench() {
-        testsPredefinedSubtypes.runInLocale(mRes, Locale.FRENCH);
-    }
-
-    public void testAdditionalSubtypeInFrench() {
-        testsAdditionalSubtypes.runInLocale(mRes, Locale.FRENCH);
-    }
-}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
similarity index 89%
rename from tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
rename to tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
index 9014e7c..2eb448c 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserCsvTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserSplitTests.java
@@ -17,11 +17,13 @@
 package com.android.inputmethod.keyboard.internal;
 
 import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Resources;
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.StringUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.RunInLocale;
 
 import java.lang.reflect.Field;
 import java.util.ArrayList;
@@ -29,21 +31,30 @@
 import java.util.Locale;
 
 @MediumTest
-public class KeySpecParserCsvTests extends InstrumentationTestCase {
-    private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+public class KeySpecParserSplitTests extends InstrumentationTestCase {
+    private static final Locale TEST_LOCALE = Locale.ENGLISH;
+    final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
         final Instrumentation instrumentation = getInstrumentation();
-        mTextsSet.setLanguage(Locale.ENGLISH.getLanguage());
-        mTextsSet.loadStringResources(instrumentation.getTargetContext());
+        final Context targetContext = instrumentation.getTargetContext();
+        mTextsSet.setLanguage(TEST_LOCALE.getLanguage());
+        new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                mTextsSet.loadStringResources(targetContext);
+                return null;
+            }
+        }.runInLocale(targetContext.getResources(), TEST_LOCALE);
         final String[] testResourceNames = getAllResourceIdNames(
                 com.android.inputmethod.latin.tests.R.string.class);
-        mTextsSet.loadStringResourcesInternal(instrumentation.getContext(),
-                testResourceNames,
-                com.android.inputmethod.latin.tests.R.string.empty_string);
+        mTextsSet.loadStringResourcesInternal(instrumentation.getContext(), testResourceNames,
+                // This dummy raw resource is needed to be able to load string resources from a test
+                // APK successfully.
+                com.android.inputmethod.latin.tests.R.raw.dummy_resource_for_testing);
     }
 
     private static String[] getAllResourceIdNames(final Class<?> resourceIdClass) {
@@ -56,8 +67,8 @@
         return names.toArray(new String[names.size()]);
     }
 
-    private static void assertArrayEquals(final String message, final Object[] expected,
-            final Object[] actual) {
+    private static <T> void assertArrayEquals(final String message, final T[] expected,
+            final T[] actual) {
         if (expected == actual) {
             return;
         }
@@ -70,15 +81,19 @@
             return;
         }
         for (int i = 0; i < expected.length; i++) {
-            assertEquals(message + " [" + i + "]",
-                    Arrays.toString(expected), Arrays.toString(actual));
+            final T e = expected[i];
+            final T a = actual[i];
+            if (e == a) {
+                continue;
+            }
+            assertEquals(message + " [" + i + "]", e, a);
         }
     }
 
     private void assertTextArray(final String message, final String value,
             final String ... expectedArray) {
         final String resolvedActual = KeySpecParser.resolveTextReference(value, mTextsSet);
-        final String[] actual = StringUtils.parseCsvString(resolvedActual);
+        final String[] actual = KeySpecParser.splitKeySpecs(resolvedActual);
         final String[] expected = (expectedArray.length == 0) ? null : expectedArray;
         assertArrayEquals(message, expected, actual);
     }
@@ -101,7 +116,7 @@
     private static final String SURROGATE1 = PAIR1 + PAIR2;
     private static final String SURROGATE2 = PAIR1 + PAIR2 + PAIR3;
 
-    public void testParseCsvTextZero() {
+    public void testSplitZero() {
         assertTextArray("Empty string", "");
         assertTextArray("Empty entry", ",");
         assertTextArray("Empty entry at beginning", ",a", "a");
@@ -110,7 +125,7 @@
         assertTextArray("Empty entries with escape", ",a,b\\,c,,d,", "a", "b\\,c", "d");
     }
 
-    public void testParseCsvTextSingle() {
+    public void testSplitSingle() {
         assertTextArray("Single char", "a", "a");
         assertTextArray("Surrogate pair", PAIR1, PAIR1);
         assertTextArray("Single escape", "\\", "\\");
@@ -139,7 +154,7 @@
         assertTextArray("Incomplete resource reference 4", "!" + SURROGATE2, "!" + SURROGATE2);
     }
 
-    public void testParseCsvTextSingleEscaped() {
+    public void testSplitSingleEscaped() {
         assertTextArray("Escaped char", "\\a", "\\a");
         assertTextArray("Escaped surrogate pair", "\\" + PAIR1, "\\" + PAIR1);
         assertTextArray("Escaped comma", "\\,", "\\,");
@@ -174,7 +189,7 @@
         assertTextArray("Escaped !TEXT/NAME", "\\!TEXT/EMPTY_STRING", "\\!TEXT/EMPTY_STRING");
     }
 
-    public void testParseCsvTextMulti() {
+    public void testSplitMulti() {
         assertTextArray("Multiple chars", "a,b,c", "a", "b", "c");
         assertTextArray("Multiple chars", "a,b,\\c", "a", "b", "\\c");
         assertTextArray("Multiple chars and escape at beginning and end",
@@ -189,7 +204,7 @@
                 " abc ", " def ", " ghi ");
     }
 
-    public void testParseCsvTextMultiEscaped() {
+    public void testSplitMultiEscaped() {
         assertTextArray("Multiple chars with comma", "a,\\,,c", "a", "\\,", "c");
         assertTextArray("Multiple chars with comma surrounded by spaces", " a , \\, , c ",
                 " a ", " \\, ", " c ");
@@ -208,17 +223,17 @@
                 "\\!", "\\!TEXT/EMPTY_STRING");
     }
 
-    public void testParseCsvResourceError() {
+    public void testSplitResourceError() {
         assertError("Incomplete resource name", "!text/", "!text/");
         assertError("Non existing resource", "!text/non_existing");
     }
 
-    public void testParseCsvResourceZero() {
+    public void testSplitResourceZero() {
         assertTextArray("Empty string",
                 "!text/empty_string");
     }
 
-    public void testParseCsvResourceSingle() {
+    public void testSplitResourceSingle() {
         assertTextArray("Single char",
                 "!text/single_char", "a");
         assertTextArray("Space",
@@ -240,7 +255,7 @@
                 "\\\\!text/single_char", "\\\\a");
     }
 
-    public void testParseCsvResourceSingleEscaped() {
+    public void testSplitResourceSingleEscaped() {
         assertTextArray("Escaped char",
                 "!text/escaped_char", "\\a");
         assertTextArray("Escaped comma",
@@ -267,7 +282,7 @@
                 "!text/escaped_label_with_escape", "a\\\\c");
     }
 
-    public void testParseCsvResourceMulti() {
+    public void testSplitResourceMulti() {
         assertTextArray("Multiple chars",
                 "!text/multiple_chars", "a", "b", "c");
         assertTextArray("Multiple chars surrounded by spaces",
@@ -279,7 +294,7 @@
                 "!text/multiple_labels_surrounded_by_spaces", " abc ", " def ", " ghi ");
     }
 
-    public void testParseCsvResourcetMultiEscaped() {
+    public void testSplitResourcetMultiEscaped() {
         assertTextArray("Multiple chars with comma",
                 "!text/multiple_chars_with_comma",
                 "a", "\\,", "c");
@@ -300,7 +315,7 @@
                 " ab\\\\ ", " d\\\\\\, ", " g\\,i ");
     }
 
-    public void testParseMultipleResources() {
+    public void testSplitMultipleResources() {
         assertTextArray("Literals and resources",
                 "1,!text/multiple_chars,z", "1", "a", "b", "c", "z");
         assertTextArray("Literals and resources and escape at end",
@@ -322,7 +337,7 @@
                 "abcabc", "def", "ghi");
     }
 
-    public void testParseIndirectReference() {
+    public void testSplitIndirectReference() {
         assertTextArray("Indirect",
                 "!text/indirect_string", "a", "b", "c");
         assertTextArray("Indirect with literal",
@@ -331,7 +346,7 @@
                 "!text/indirect2_string", "a", "b", "c");
     }
 
-    public void testParseInfiniteIndirectReference() {
+    public void testSplitInfiniteIndirectReference() {
         assertError("Infinite indirection",
                 "1,!text/infinite_indirection,2", "1", "infinite", "<infinite>", "loop", "2");
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
index b1ae6f5..afb2b03 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java
@@ -20,23 +20,27 @@
 import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT;
 import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;
 
+import android.content.Context;
+import android.content.res.Resources;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.RunInLocale;
 
 import java.util.Arrays;
 import java.util.Locale;
 
 @SmallTest
 public class KeySpecParserTests extends AndroidTestCase {
-    private final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
-    private final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
+    private final static Locale TEST_LOCALE = Locale.ENGLISH;
+    final KeyboardCodesSet mCodesSet = new KeyboardCodesSet();
+    final KeyboardTextsSet mTextsSet = new KeyboardTextsSet();
 
     private static final String CODE_SETTINGS = "!code/key_settings";
     private static final String ICON_SETTINGS = "!icon/settings_key";
-    private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase();
-    private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase();
+    private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT);
+    private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT);
     private static final String CODE_NON_EXISTING = "!code/non_existing";
     private static final String ICON_NON_EXISTING = "!icon/non_existing";
 
@@ -48,10 +52,17 @@
     protected void setUp() throws Exception {
         super.setUp();
 
-        final String language = Locale.ENGLISH.getLanguage();
+        final String language = TEST_LOCALE.getLanguage();
         mCodesSet.setLanguage(language);
         mTextsSet.setLanguage(language);
-        mTextsSet.loadStringResources(getContext());
+        final Context context = getContext();
+        new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                mTextsSet.loadStringResources(context);
+                return null;
+            }
+        }.runInLocale(context.getResources(), TEST_LOCALE);
 
         mCodeSettings = KeySpecParser.parseCode(
                 CODE_SETTINGS, mCodesSet, CODE_UNSPECIFIED);
@@ -587,7 +598,7 @@
                 new String[] { null, "a", "b", "c" }, true);
         // Upper case specification will not work.
         assertGetBooleanValue("HAS LABEL", HAS_LABEL,
-                new String[] { HAS_LABEL.toUpperCase(), "a", "b", "c" },
+                new String[] { HAS_LABEL.toUpperCase(Locale.ROOT), "a", "b", "c" },
                 new String[] { "!HASLABEL!", "a", "b", "c" }, false);
 
         assertGetBooleanValue("No has label", HAS_LABEL,
@@ -600,13 +611,13 @@
         // Upper case specification will not work.
         assertGetBooleanValue("Multiple has label", HAS_LABEL,
                 new String[] {
-                    "a", HAS_LABEL.toUpperCase(), "b", "c", HAS_LABEL, "d" },
+                    "a", HAS_LABEL.toUpperCase(Locale.ROOT), "b", "c", HAS_LABEL, "d" },
                 new String[] {
                     "a", "!HASLABEL!", "b", "c", null, "d" }, true);
         // Upper case specification will not work.
         assertGetBooleanValue("Multiple has label with needs dividers", HAS_LABEL,
                 new String[] {
-                    "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(), "d" },
+                    "a", HAS_LABEL, "b", NEEDS_DIVIDER, HAS_LABEL.toUpperCase(Locale.ROOT), "d" },
                 new String[] {
                     "a", null, "b", NEEDS_DIVIDER, "!HASLABEL!", "d" }, true);
     }
@@ -625,7 +636,7 @@
                 new String[] { null, "a", "b", "c" }, 3);
         // Upper case specification will not work.
         assertGetIntValue("FIXED COLUMN ORDER 3", FIXED_COLUMN_ORDER, -1,
-                new String[] { FIXED_COLUMN_ORDER.toUpperCase() + "3", "a", "b", "c" },
+                new String[] { FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "3", "a", "b", "c" },
                 new String[] { "!FIXEDCOLUMNORDER!3", "a", "b", "c" }, -1);
 
         assertGetIntValue("No fixed column order", FIXED_COLUMN_ORDER, -1,
@@ -641,7 +652,7 @@
         // Upper case specification will not work.
         assertGetIntValue("Multiple fixed column order 5,3 with has label", FIXED_COLUMN_ORDER, -1,
                 new String[] {
-                    FIXED_COLUMN_ORDER.toUpperCase() + "5", HAS_LABEL, "a",
+                    FIXED_COLUMN_ORDER.toUpperCase(Locale.ROOT) + "5", HAS_LABEL, "a",
                     FIXED_COLUMN_ORDER + "3", "b" },
                 new String[] { "!FIXEDCOLUMNORDER!5", HAS_LABEL, "a", null, "b" }, 3);
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
index b193e66..9ad81c0 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -60,7 +60,7 @@
     // Chording input in shift locked.
     public void testChordingShiftLocked() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press shift key and hold, enter into choring shift state.
@@ -119,7 +119,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -137,7 +137,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -196,7 +196,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -216,7 +216,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -397,29 +397,29 @@
 
     public void testLongPressShiftAndChording() {
         // Long press shift key, enter maybe shift locked.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet (not shift locked).
         releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Long press shift key, enter maybe alphabet.
-        longPressKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
+        longPressShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
         // Release shift key, back to shift locked (not alphabet).
         releaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED);
         // Long press shift key, enter alphabet
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release shift key, enter alphabet shifted.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Long press shift key, enter maybe alphabet.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet shifted (not alphabet).
@@ -430,7 +430,7 @@
         // Load keyboard, should be in automatic shifted.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
         // Long press shift key, enter maybe shift locked.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet (not shift locked).
@@ -449,7 +449,7 @@
 //        releaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
 //
 //        // Long press shift key, enter alphabet shift locked.
-//        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+//        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
 //                ALPHABET_SHIFT_LOCKED);
 //        // First shift key tap.
 //        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index d5b9d1d..c7ac76d 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -91,7 +91,7 @@
     // Switching between alphabet shift locked and symbols.
     public void testAlphabetShiftLockedAndSymbols() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press/release "?123" key, enter into symbols.
@@ -133,7 +133,7 @@
     // Automatic switch back to alphabet shift locked test by space key.
     public void testSwitchBackBySpaceShiftLocked() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press/release "?123" key, enter into symbols.
@@ -196,13 +196,13 @@
         // Load keyboard, should be in alphabet.
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release letter key, remain in shift locked.
         pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
@@ -212,16 +212,16 @@
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release shift key, enter alphabet shifted.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
@@ -231,7 +231,7 @@
         // Load keyboard, should be in automatic shifted.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
@@ -293,12 +293,12 @@
         updateShiftState(ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Update shift state, remained in alphabet shift locked.
         updateShiftState(ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release "?123" key, enter into symbols.
@@ -326,12 +326,12 @@
         updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Update shift state, remained in alphabet shift locked (not automatic shifted).
         updateShiftState(ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Load keyboard, should be in automatic shifted.
@@ -383,7 +383,7 @@
 
         // Alphabet shift locked -> shift key + letter -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press and slide from "123?" key, enter symbols.
         pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -441,7 +441,7 @@
 
         // Alphabet shift locked -> shift key + letter -> cancel -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press and slide from "123?" key, enter symbols.
         pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -500,7 +500,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -517,7 +517,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -574,7 +574,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -592,7 +592,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -651,7 +651,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -670,7 +670,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -733,7 +733,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -753,7 +753,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -777,7 +777,7 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Change focus to new text field.
         loadKeyboard(ALPHABET_UNSHIFTED);
@@ -808,7 +808,7 @@
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Change focus to new text field.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
@@ -852,7 +852,7 @@
 
         // Alphabet shift locked -> rotate -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Rotate device, remain in alphabet shift locked.
         rotateDevice(ALPHABET_SHIFT_LOCKED);
@@ -936,7 +936,7 @@
         secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key to enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
index e06ca06..3ffd0a9 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -18,6 +18,8 @@
 
 import android.test.AndroidTestCase;
 
+import com.android.inputmethod.latin.Constants;
+
 public class KeyboardStateTestsBase extends AndroidTestCase
         implements MockKeyboardSwitcher.MockConstants {
     protected MockKeyboardSwitcher mSwitcher;
@@ -32,6 +34,11 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
     }
 
+    /**
+     * Set auto caps mode.
+     *
+     * @param autoCaps the auto cap mode.
+     */
     public void setAutoCapsMode(final int autoCaps) {
         mSwitcher.setAutoCapsMode(autoCaps);
     }
@@ -42,17 +49,32 @@
                 expected == actual);
     }
 
+    /**
+     * Emulate update keyboard shift state.
+     *
+     * @param afterUpdate the keyboard state after updating the keyboard shift state.
+     */
     public void updateShiftState(final int afterUpdate) {
         mSwitcher.updateShiftState();
         assertLayout("afterUpdate", afterUpdate, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate load default keyboard.
+     *
+     * @param afterLoad the keyboard state after loading default keyboard.
+     */
     public void loadKeyboard(final int afterLoad) {
         mSwitcher.loadKeyboard();
         mSwitcher.updateShiftState();
         assertLayout("afterLoad", afterLoad, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate rotate device.
+     *
+     * @param afterRotate the keyboard state after rotating device.
+     */
     public void rotateDevice(final int afterRotate) {
         mSwitcher.saveKeyboardState();
         mSwitcher.loadKeyboard();
@@ -65,45 +87,97 @@
         assertLayout("afterPress", afterPress, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate key press.
+     *
+     * @param code the key code to press.
+     * @param afterPress the keyboard state after pressing the key.
+     */
     public void pressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
+    /**
+     * Emulate key release and register.
+     *
+     * @param code the key code to release and register
+     * @param afterRelease the keyboard state after releasing the key.
+     */
     public void releaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate key press and release.
+     *
+     * @param code the key code to press and release.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterRelease the keyboard state after releasing the key.
+     */
     public void pressAndReleaseKey(final int code, final int afterPress, final int afterRelease) {
         pressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
 
+    /**
+     * Emulate chording key press.
+     *
+     * @param code the chording key code.
+     * @param afterPress the keyboard state after pressing chording key.
+     */
     public void chordingPressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, false, afterPress);
     }
 
+    /**
+     * Emulate chording key release.
+     *
+     * @param code the cording key code.
+     * @param afterRelease the keyboard state after releasing chording key.
+     */
     public void chordingReleaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate chording key press and release.
+     *
+     * @param code the chording key code.
+     * @param afterPress the keyboard state after pressing chording key.
+     * @param afterRelease the keyboard state after releasing chording key.
+     */
     public void chordingPressAndReleaseKey(final int code, final int afterPress,
             final int afterRelease) {
         chordingPressKey(code, afterPress);
         chordingReleaseKey(code, afterRelease);
     }
 
+    /**
+     * Emulate start of the sliding key input.
+     *
+     * @param code the key code to start sliding.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterSlide the keyboard state after releasing the key with sliding input.
+     */
     public void pressAndSlideFromKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onReleaseKey(code, SLIDING);
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate end of the sliding key input.
+     *
+     * @param code the key code to stop sliding.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterSlide the keyboard state after releasing the key and stop sliding.
+     */
     public void stopSlidingOnKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onCodeInput(code);
@@ -112,28 +186,67 @@
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate cancel the sliding key input.
+     *
+     * @param afterCancelSliding the keyboard state after canceling sliding input.
+     */
     public void stopSlidingAndCancel(final int afterCancelSliding) {
         mSwitcher.onFinishSlidingInput();
         assertLayout("afterCancelSliding", afterCancelSliding, mSwitcher.getLayoutId());
     }
 
-    public void longPressKey(final int code, final int afterPress, final int afterLongPress) {
-        pressKey(code, afterPress);
-        mSwitcher.onLongPressTimeout(code);
+    /**
+     * Emulate long press shift key.
+     *
+     * @param afterPress the keyboard state after pressing shift key.
+     * @param afterLongPress the keyboard state after long press fired.
+     */
+    public void longPressShiftKey(final int afterPress, final int afterLongPress) {
+        // Long press shift key will register {@link Constants#CODE_CAPS_LOCK}. See
+        // {@link R.xml#key_styles_common} and its baseForShiftKeyStyle. We thus emulate the
+        // behavior that is implemented in {@link MainKeyboardView#onLongPress(PointerTracker)}.
+        pressKey(Constants.CODE_SHIFT, afterPress);
+        mSwitcher.onPressKey(Constants.CODE_CAPSLOCK, true /* isSinglePointer */);
+        mSwitcher.onCodeInput(Constants.CODE_CAPSLOCK);
         assertLayout("afterLongPress", afterLongPress, mSwitcher.getLayoutId());
     }
 
-    public void longPressAndReleaseKey(final int code, final int afterPress,
-            final int afterLongPress, final int afterRelease) {
-        longPressKey(code, afterPress, afterLongPress);
-        releaseKey(code, afterRelease);
+    /**
+     * Emulate long press shift key and release.
+     *
+     * @param afterPress the keyboard state after pressing shift key.
+     * @param afterLongPress the keyboard state after long press fired.
+     * @param afterRelease the keyboard state after shift key is released.
+     */
+    public void longPressAndReleaseShiftKey(final int afterPress, final int afterLongPress,
+            final int afterRelease) {
+        // Long press shift key will register {@link Constants#CODE_CAPS_LOCK}. See
+        // {@link R.xml#key_styles_common} and its baseForShiftKeyStyle. We thus emulate the
+        // behavior that is implemented in {@link MainKeyboardView#onLongPress(PointerTracker)}.
+        longPressShiftKey(afterPress, afterLongPress);
+        releaseKey(Constants.CODE_CAPSLOCK, afterRelease);
     }
 
-    public void secondPressKey(int code, int afterPress) {
+    /**
+     * Emulate the second press of the double tap.
+     *
+     * @param code the key code to double tap.
+     * @param afterPress the keyboard state after pressing the second tap.
+     */
+    public void secondPressKey(final int code, final int afterPress) {
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
-    public void secondPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    /**
+     * Emulate the second tap of the double tap.
+     *
+     * @param code the key code to double tap.
+     * @param afterPress the keyboard state after pressing the second tap.
+     * @param afterRelease the keyboard state after releasing the second tap.
+     */
+    public void secondPressAndReleaseKey(final int code, final int afterPress,
+            final int afterRelease) {
         secondPressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java b/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java
new file mode 100644
index 0000000..e2a11ab
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MatrixUtilsTests.java
@@ -0,0 +1,84 @@
+/*
+ * 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.keyboard.internal.MatrixUtils.MatrixOperationFailedException;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class MatrixUtilsTests extends AndroidTestCase {
+    // "run tests" -c com.android.inputmethod.keyboard.internal.MatrixUtilsTests
+    private static final boolean DEBUG = false;
+    private static final float EPSILON = 0.00001f;
+
+    private static void assertEqualsFloat(float f0, float f1) {
+        assertEqualsFloat(f0, f1, EPSILON);
+    }
+
+    /* package */ static void assertEqualsFloat(float f0, float f1, float error) {
+        assertTrue(Math.abs(f0 - f1) < error);
+    }
+
+    public void testMulti() {
+        final float[][] matrixA = {{1, 2}, {3, 4}};
+        final float[][] matrixB = {{5, 6}, {7, 8}};
+        final float[][] retval = new float[2][2];
+        try {
+            MatrixUtils.multiply(matrixA, matrixB, retval);
+        } catch (MatrixOperationFailedException e) {
+            assertTrue(false);
+        }
+        if (DEBUG) {
+            MatrixUtils.dump("multi", retval);
+        }
+        assertEqualsFloat(retval[0][0], 19);
+        assertEqualsFloat(retval[0][1], 22);
+        assertEqualsFloat(retval[1][0], 43);
+        assertEqualsFloat(retval[1][1], 50);
+    }
+
+    public void testInverse() {
+        final int N = 4;
+        final float[][] matrix =
+                {{1, 2, 3, 4}, {4, 0, 5, 6}, {6, 4, 2, 0}, {6, 4, 2, 1}};
+        final float[][] inverse = new float[N][N];
+        final float[][] tempMatrix = new float[N][N];
+        for (int i = 0; i < N; ++i) {
+            for (int j = 0; j < N; ++j) {
+                tempMatrix[i][j] = matrix[i][j];
+            }
+        }
+        final float[][] retval = new float[N][N];
+        try {
+            MatrixUtils.inverse(tempMatrix, inverse);
+        } catch (MatrixOperationFailedException e) {
+            assertTrue(false);
+        }
+        try {
+            MatrixUtils.multiply(matrix, inverse, retval);
+        } catch (MatrixOperationFailedException e) {
+            assertTrue(false);
+        }
+        for (int i = 0; i < N; ++i) {
+            for (int j = 0; j < N; ++j) {
+                assertEqualsFloat(((i == j) ? 1.0f : 0.0f), retval[i][j]);
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
index 2544b6c..db39976 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java
@@ -19,7 +19,7 @@
 import android.text.TextUtils;
 
 import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.RecapitalizeStatus;
+import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 
 public class MockKeyboardSwitcher implements KeyboardState.SwitchActions {
     public interface MockConstants {
@@ -53,7 +53,7 @@
     // Following InputConnection's behavior. Simulating InputType.TYPE_TEXT_FLAG_CAP_WORDS.
     private int mAutoCapsState = MockConstants.CAP_MODE_OFF;
 
-    private boolean mIsInDoubleTapTimeout;
+    private boolean mIsInDoubleTapShiftKeyTimeout;
     private int mLongPressTimeoutCode;
 
     private final KeyboardState mState = new KeyboardState(this);
@@ -81,7 +81,7 @@
     }
 
     public void expireDoubleTapTimeout() {
-        mIsInDoubleTapTimeout = false;
+        mIsInDoubleTapShiftKeyTimeout = false;
     }
 
     @Override
@@ -125,41 +125,18 @@
     }
 
     @Override
-    public void startDoubleTapTimer() {
-        mIsInDoubleTapTimeout = true;
+    public void startDoubleTapShiftKeyTimer() {
+        mIsInDoubleTapShiftKeyTimeout = true;
     }
 
     @Override
-    public void cancelDoubleTapTimer() {
-        mIsInDoubleTapTimeout = false;
+    public void cancelDoubleTapShiftKeyTimer() {
+        mIsInDoubleTapShiftKeyTimeout = false;
     }
 
     @Override
-    public boolean isInDoubleTapTimeout() {
-        return mIsInDoubleTapTimeout;
-    }
-
-    @Override
-    public void startLongPressTimer(final int code) {
-        mLongPressTimeoutCode = code;
-    }
-
-    @Override
-    public void cancelLongPressTimer() {
-        mLongPressTimeoutCode = 0;
-    }
-
-    @Override
-    public void hapticAndAudioFeedback(final int code) {
-        // Nothing to do.
-    }
-
-    public void onLongPressTimeout(final int code) {
-        // TODO: Handle simultaneous long presses.
-        if (mLongPressTimeoutCode == code) {
-            mLongPressTimeoutCode = 0;
-            mState.onLongPressTimeout(code);
-        }
+    public boolean isInDoubleTapShiftKeyTimeout() {
+        return mIsInDoubleTapShiftKeyTimeout;
     }
 
     public void updateShiftState() {
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java b/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java
new file mode 100644
index 0000000..293741a
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/internal/SmoothingUtilsTests.java
@@ -0,0 +1,45 @@
+/*
+ * 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.keyboard.internal.MatrixUtils.MatrixOperationFailedException;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+@SmallTest
+public class SmoothingUtilsTests extends AndroidTestCase {
+    // "run tests" -c com.android.inputmethod.keyboard.internal.SmoothingUtilsTests
+    private static final boolean DEBUG = false;
+
+    public void testGet3DParamaters() {
+        final float[] xs = new float[] {0, 1, 2, 3, 4};
+        final float[] ys = new float[] {1, 4, 15, 40, 85}; // y = x^3 + x^2 + x + 1
+        final float[][] retval = new float[4][1];
+        try {
+            SmoothingUtils.get3DParameters(xs, ys, retval);
+            if (DEBUG) {
+                MatrixUtils.dump("3d", retval);
+            }
+            for (int i = 0; i < 4; ++i) {
+                MatrixUtilsTests.assertEqualsFloat(retval[i][0], 1.0f, 0.001f);
+            }
+        } catch (MatrixOperationFailedException e) {
+            assertTrue(false);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/InputPointersTests.java b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
index e1149b3..f0b6acc 100644
--- a/tests/src/com/android/inputmethod/latin/InputPointersTests.java
+++ b/tests/src/com/android/inputmethod/latin/InputPointersTests.java
@@ -19,6 +19,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.utils.ResizableIntArray;
+
 import java.util.Arrays;
 
 @SmallTest
@@ -108,13 +110,13 @@
         assertNotSame("pointerIds after copy", dst.getPointerIds(), src.getPointerIds());
         assertNotSame("times after copy", dst.getTimes(), src.getTimes());
         final int size = dst.getPointerSize();
-        assertArrayEquals("xCoordinates values after copy",
+        assertIntArrayEquals("xCoordinates values after copy",
                 dst.getXCoordinates(), 0, src.getXCoordinates(), 0, size);
-        assertArrayEquals("yCoordinates values after copy",
+        assertIntArrayEquals("yCoordinates values after copy",
                 dst.getYCoordinates(), 0, src.getYCoordinates(), 0, size);
-        assertArrayEquals("pointerIds values after copy",
+        assertIntArrayEquals("pointerIds values after copy",
                 dst.getPointerIds(), 0, src.getPointerIds(), 0, size);
-        assertArrayEquals("times values after copy",
+        assertIntArrayEquals("times values after copy",
                 dst.getTimes(), 0, src.getTimes(), 0, size);
     }
 
@@ -135,34 +137,34 @@
 
         dst.append(src, 0, 0);
         assertEquals("size after append zero", dstLen, dst.getPointerSize());
-        assertArrayEquals("xCoordinates after append zero",
+        assertIntArrayEquals("xCoordinates after append zero",
                 dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
-        assertArrayEquals("yCoordinates after append zero",
+        assertIntArrayEquals("yCoordinates after append zero",
                 dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
-        assertArrayEquals("pointerIds after append zero",
+        assertIntArrayEquals("pointerIds after append zero",
                 dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
-        assertArrayEquals("times after append zero",
+        assertIntArrayEquals("times after append zero",
                 dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
 
         dst.append(src, 0, srcLen);
         assertEquals("size after append", dstLen + srcLen, dst.getPointerSize());
         assertTrue("primitive length after append",
                 dst.getPointerIds().length >= dstLen + srcLen);
-        assertArrayEquals("original xCoordinates values after append",
+        assertIntArrayEquals("original xCoordinates values after append",
                 dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
-        assertArrayEquals("original yCoordinates values after append",
+        assertIntArrayEquals("original yCoordinates values after append",
                 dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
-        assertArrayEquals("original pointerIds values after append",
+        assertIntArrayEquals("original pointerIds values after append",
                 dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
-        assertArrayEquals("original times values after append",
+        assertIntArrayEquals("original times values after append",
                 dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
-        assertArrayEquals("appended xCoordinates values after append",
+        assertIntArrayEquals("appended xCoordinates values after append",
                 src.getXCoordinates(), 0, dst.getXCoordinates(), dstLen, srcLen);
-        assertArrayEquals("appended yCoordinates values after append",
+        assertIntArrayEquals("appended yCoordinates values after append",
                 src.getYCoordinates(), 0, dst.getYCoordinates(), dstLen, srcLen);
-        assertArrayEquals("appended pointerIds values after append",
+        assertIntArrayEquals("appended pointerIds values after append",
                 src.getPointerIds(), 0, dst.getPointerIds(), dstLen, srcLen);
-        assertArrayEquals("appended times values after append",
+        assertIntArrayEquals("appended times values after append",
                 src.getTimes(), 0, dst.getTimes(), dstLen, srcLen);
     }
 
@@ -190,47 +192,55 @@
 
         dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, 0);
         assertEquals("size after append zero", dstLen, dst.getPointerSize());
-        assertArrayEquals("xCoordinates after append zero",
+        assertIntArrayEquals("xCoordinates after append zero",
                 dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
-        assertArrayEquals("yCoordinates after append zero",
+        assertIntArrayEquals("yCoordinates after append zero",
                 dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
-        assertArrayEquals("pointerIds after append zero",
+        assertIntArrayEquals("pointerIds after append zero",
                 dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
-        assertArrayEquals("times after append zero",
+        assertIntArrayEquals("times after append zero",
                 dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
 
         dst.append(srcPointerId, srcTimes, srcXCoords, srcYCoords, 0, srcLen);
         assertEquals("size after append", dstLen + srcLen, dst.getPointerSize());
         assertTrue("primitive length after append",
                 dst.getPointerIds().length >= dstLen + srcLen);
-        assertArrayEquals("original xCoordinates values after append",
+        assertIntArrayEquals("original xCoordinates values after append",
                 dstCopy.getXCoordinates(), 0, dst.getXCoordinates(), 0, dstLen);
-        assertArrayEquals("original yCoordinates values after append",
+        assertIntArrayEquals("original yCoordinates values after append",
                 dstCopy.getYCoordinates(), 0, dst.getYCoordinates(), 0, dstLen);
-        assertArrayEquals("original pointerIds values after append",
+        assertIntArrayEquals("original pointerIds values after append",
                 dstCopy.getPointerIds(), 0, dst.getPointerIds(), 0, dstLen);
-        assertArrayEquals("original times values after append",
+        assertIntArrayEquals("original times values after append",
                 dstCopy.getTimes(), 0, dst.getTimes(), 0, dstLen);
-        assertArrayEquals("appended xCoordinates values after append",
+        assertIntArrayEquals("appended xCoordinates values after append",
                 srcXCoords.getPrimitiveArray(), 0, dst.getXCoordinates(), dstLen, srcLen);
-        assertArrayEquals("appended yCoordinates values after append",
+        assertIntArrayEquals("appended yCoordinates values after append",
                 srcYCoords.getPrimitiveArray(), 0, dst.getYCoordinates(), dstLen, srcLen);
-        assertArrayEquals("appended pointerIds values after append",
+        assertIntArrayEquals("appended pointerIds values after append",
                 srcPointerIds, 0, dst.getPointerIds(), dstLen, srcLen);
-        assertArrayEquals("appended times values after append",
+        assertIntArrayEquals("appended times values after append",
                 srcTimes.getPrimitiveArray(), 0, dst.getTimes(), dstLen, srcLen);
     }
 
-    private static void assertArrayEquals(String message, int[] expecteds, int expectedPos,
-            int[] actuals, int actualPos, int length) {
-        if (expecteds == null && actuals == null) {
+    // TODO: Consolidate this method with
+    // {@link ResizableIntArrayTests#assertIntArrayEquals(String,int[],int,int[],int,int)}.
+    private static void assertIntArrayEquals(final String message, final int[] expecteds,
+            final int expectedPos, final int[] actuals, final int actualPos, final int length) {
+        if (expecteds == actuals) {
             return;
         }
         if (expecteds == null || actuals == null) {
-            fail(message + ": expecteds=" + expecteds + " actuals=" + actuals);
+            assertEquals(message, Arrays.toString(expecteds), Arrays.toString(actuals));
+            return;
+        }
+        if (expecteds.length < expectedPos + length || actuals.length < actualPos + length) {
+            fail(message + ": insufficient length: expecteds=" + Arrays.toString(expecteds)
+                    + " actuals=" + Arrays.toString(actuals));
+            return;
         }
         for (int i = 0; i < length; i++) {
-            assertEquals(message + ": element at " + i,
+            assertEquals(message + " [" + i + "]",
                     expecteds[i + expectedPos], actuals[i + actualPos]);
         }
     }
diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java
index aec4aac..eb4f706 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 com.android.inputmethod.latin.utils.LocaleUtils;
 
 import java.util.Locale;
 
@@ -224,7 +225,7 @@
 
     protected void waitForDictionaryToBeLoaded() {
         int remainingAttempts = 300;
-        while (remainingAttempts > 0 && mLatinIME.mSuggest.isCurrentlyWaitingForMainDictionary()) {
+        while (remainingAttempts > 0 && mLatinIME.isCurrentlyWaitingForMainDictionary()) {
             try {
                 Thread.sleep(200);
             } catch (InterruptedException e) {
@@ -233,7 +234,7 @@
                 --remainingAttempts;
             }
         }
-        if (!mLatinIME.mSuggest.hasMainDictionary()) {
+        if (!mLatinIME.hasMainDictionary()) {
             throw new RuntimeException("Can't initialize the main dictionary");
         }
     }
@@ -242,6 +243,7 @@
         mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale);
         SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale);
         mLatinIME.loadKeyboard();
+        runMessages();
         mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard();
         waitForDictionaryToBeLoaded();
     }
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
new file mode 100644
index 0000000..c0dd993
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import com.android.inputmethod.latin.utils.TextRange;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.text.style.SuggestionSpan;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+
+import java.util.Locale;
+
+@SmallTest
+public class RichInputConnectionAndTextRangeTests extends AndroidTestCase {
+
+    // The following is meant to be a reasonable default for
+    // the "word_separators" resource.
+    private static final String sSeparators = ".,:;!?-";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    private class MockConnection extends InputConnectionWrapper {
+        final CharSequence mTextBefore;
+        final CharSequence mTextAfter;
+        final ExtractedText mExtractedText;
+
+        public MockConnection(final CharSequence text, final int cursorPosition) {
+            super(null, false);
+            // Interaction of spans with Parcels is completely non-trivial, but in the actual case
+            // the CharSequences do go through Parcels because they go through IPC. There
+            // are some significant differences between the behavior of Spanned objects that
+            // have and that have not gone through parceling, so it's much easier to simulate
+            // the environment with Parcels than try to emulate things by hand.
+            final Parcel p = Parcel.obtain();
+            TextUtils.writeToParcel(text.subSequence(0, cursorPosition), p, 0 /* flags */);
+            TextUtils.writeToParcel(text.subSequence(cursorPosition, text.length()), p,
+                    0 /* flags */);
+            final byte[] marshalled = p.marshall();
+            p.unmarshall(marshalled, 0, marshalled.length);
+            p.setDataPosition(0);
+            mTextBefore = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
+            mTextAfter = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
+            mExtractedText = null;
+            p.recycle();
+        }
+
+        public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) {
+            super(null, false);
+            mTextBefore = textBefore;
+            mTextAfter = textAfter;
+            mExtractedText = extractedText;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
+         */
+        @Override
+        public CharSequence getTextBeforeCursor(int n, int flags) {
+            return mTextBefore;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int)
+         */
+        @Override
+        public CharSequence getTextAfterCursor(int n, int flags) {
+            return mTextAfter;
+        }
+
+        /* (non-Javadoc)
+         * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(
+         *         ExtractedTextRequest, int)
+         */
+        @Override
+        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+            return mExtractedText;
+        }
+
+        @Override
+        public boolean beginBatchEdit() {
+            return true;
+        }
+
+        @Override
+        public boolean endBatchEdit() {
+            return true;
+        }
+
+        @Override
+        public boolean finishComposingText() {
+            return true;
+        }
+    }
+
+    private class MockInputMethodService extends InputMethodService {
+        InputConnection mInputConnection;
+        public void setInputConnection(final InputConnection inputConnection) {
+            mInputConnection = inputConnection;
+        }
+        @Override
+        public InputConnection getCurrentInputConnection() {
+            return mInputConnection;
+        }
+    }
+
+    /************************** Tests ************************/
+
+    /**
+     * Test for getting previous word (for bigram suggestions)
+     */
+    public void testGetPreviousWord() {
+        // If one of the following cases breaks, the bigram suggestions won't work.
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc");
+        assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2));
+        assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2));
+
+        // The following tests reflect the current behavior of the function
+        // RichInputConnection#getNthPreviousWord.
+        // TODO: However at this time, the code does never go
+        // into such a path, so it should be safe to change the behavior of
+        // this function if needed - especially since it does not seem very
+        // logical. These tests are just there to catch any unintentional
+        // changes in the behavior of the RichInputConnection#getPreviousWord method.
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2));
+
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def");
+        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def");
+        assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1));
+        assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1));
+    }
+
+    /**
+     * Test logic in getting the word range at the cursor.
+     */
+    public void testGetWordRangeAtCursor() {
+        ExtractedText et = new ExtractedText();
+        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
+        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
+        mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
+        et.startOffset = 0;
+        et.selectionStart = 7;
+        TextRange r;
+
+        ic.beginBatchEdit();
+        // basic case
+        r = ic.getWordRangeAtCursor(" ", 0);
+        assertTrue(TextUtils.equals("word", r.mWord));
+
+        // more than one word
+        r = ic.getWordRangeAtCursor(" ", 1);
+        assertTrue(TextUtils.equals("word word", r.mWord));
+        ic.endBatchEdit();
+
+        // tab character instead of space
+        mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor("\t", 1);
+        ic.endBatchEdit();
+        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();
+        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();
+        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();
+        assertTrue(TextUtils.equals("one word\tword", r.mWord));
+
+        // splitting on supplementary character
+        final String supplementaryChar = "\uD840\uDC8A";
+        mockInputMethodService.setInputConnection(
+                new MockConnection("one word" + supplementaryChar + "wo", "rd", et));
+        ic.beginBatchEdit();
+        r = ic.getWordRangeAtCursor(supplementaryChar, 0);
+        ic.endBatchEdit();
+        assertTrue(TextUtils.equals("word", r.mWord));
+    }
+
+    /**
+     * Test logic in getting the word range at the cursor.
+     */
+    public void testGetSuggestionSpansAtWord() {
+        helpTestGetSuggestionSpansAtWord(10);
+        helpTestGetSuggestionSpansAtWord(12);
+        helpTestGetSuggestionSpansAtWord(15);
+        helpTestGetSuggestionSpansAtWord(16);
+    }
+
+    private void helpTestGetSuggestionSpansAtWord(final int cursorPos) {
+        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
+        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
+
+        final String[] SUGGESTIONS1 = { "swing", "strong" };
+        final String[] SUGGESTIONS2 = { "storing", "strung" };
+
+        // Test the usual case.
+        SpannableString text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        TextRange r;
+        SuggestionSpan[] suggestions;
+
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 1);
+        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
+
+        // Test the case with 2 suggestion spans in the same place.
+        text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 2);
+        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
+        MoreAsserts.assertEquals(suggestions[1].getSuggestions(), SUGGESTIONS2);
+
+        // Test a case with overlapping spans, 2nd extending past the start of the word
+        text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
+                5 /* start */, 16 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 1);
+        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
+
+        // Test a case with overlapping spans, 2nd extending past the end of the word
+        text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
+                10 /* start */, 20 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 1);
+        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
+
+        // Test a case with overlapping spans, 2nd extending past both ends of the word
+        text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                10 /* start */, 16 /* end */, 0 /* flags */);
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
+                5 /* start */, 20 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 1);
+        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
+
+        // Test a case with overlapping spans, none right on the word
+        text = new SpannableString("This is a string for test");
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
+                5 /* start */, 16 /* end */, 0 /* flags */);
+        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
+                5 /* start */, 20 /* end */, 0 /* flags */);
+        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
+        r = ic.getWordRangeAtCursor(" ", 0);
+        suggestions = r.getSuggestionSpansAtWord();
+        assertEquals(suggestions.length, 0);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
deleted file mode 100644
index aacd60f..0000000
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionTests.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-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;
-import android.view.inputmethod.InputConnectionWrapper;
-
-import com.android.inputmethod.latin.RichInputConnection.Range;
-
-@SmallTest
-public class RichInputConnectionTests extends AndroidTestCase {
-
-    // The following is meant to be a reasonable default for
-    // the "word_separators" resource.
-    private static final String sSeparators = ".,:;!?-";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    private class MockConnection extends InputConnectionWrapper {
-        final String mTextBefore;
-        final String mTextAfter;
-        final ExtractedText mExtractedText;
-
-        public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) {
-            super(null, false);
-            mTextBefore = textBefore;
-            mTextAfter = textAfter;
-            mExtractedText = extractedText;
-        }
-
-        /* (non-Javadoc)
-         * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
-         */
-        @Override
-        public CharSequence getTextBeforeCursor(int n, int flags) {
-            return mTextBefore;
-        }
-
-        /* (non-Javadoc)
-         * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int)
-         */
-        @Override
-        public CharSequence getTextAfterCursor(int n, int flags) {
-            return mTextAfter;
-        }
-
-        /* (non-Javadoc)
-         * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(
-         *         ExtractedTextRequest, int)
-         */
-        @Override
-        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-            return mExtractedText;
-        }
-
-        @Override
-        public boolean beginBatchEdit() {
-            return true;
-        }
-
-        @Override
-        public boolean endBatchEdit() {
-            return true;
-        }
-
-        @Override
-        public boolean finishComposingText() {
-            return true;
-        }
-    }
-
-    private class MockInputMethodService extends InputMethodService {
-        InputConnection mInputConnection;
-        public void setInputConnection(final InputConnection inputConnection) {
-            mInputConnection = inputConnection;
-        }
-        @Override
-        public InputConnection getCurrentInputConnection() {
-            return mInputConnection;
-        }
-    }
-
-    /************************** Tests ************************/
-
-    /**
-     * Test for getting previous word (for bigram suggestions)
-     */
-    public void testGetPreviousWord() {
-        // If one of the following cases breaks, the bigram suggestions won't work.
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 2), "abc");
-        assertNull(RichInputConnection.getNthPreviousWord("abc", sSeparators, 2));
-        assertNull(RichInputConnection.getNthPreviousWord("abc. def", sSeparators, 2));
-
-        // The following tests reflect the current behavior of the function
-        // RichInputConnection#getNthPreviousWord.
-        // TODO: However at this time, the code does never go
-        // into such a path, so it should be safe to change the behavior of
-        // this function if needed - especially since it does not seem very
-        // logical. These tests are just there to catch any unintentional
-        // changes in the behavior of the RichInputConnection#getPreviousWord method.
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 2), "abc");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 2), "def");
-        assertNull(RichInputConnection.getNthPreviousWord("abc ", sSeparators, 2));
-
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def", sSeparators, 1), "def");
-        assertEquals(RichInputConnection.getNthPreviousWord("abc def ", sSeparators, 1), "def");
-        assertNull(RichInputConnection.getNthPreviousWord("abc def.", sSeparators, 1));
-        assertNull(RichInputConnection.getNthPreviousWord("abc def .", sSeparators, 1));
-    }
-
-    /**
-     * Test logic in getting the word range at the cursor.
-     */
-    public void testGetWordRangeAtCursor() {
-        ExtractedText et = new ExtractedText();
-        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
-        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
-        mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
-        et.startOffset = 0;
-        et.selectionStart = 7;
-        Range r;
-
-        ic.beginBatchEdit();
-        // basic case
-        r = ic.getWordRangeAtCursor(" ", 0);
-        assertTrue(TextUtils.equals("word", r.mWord));
-
-        // more than one word
-        r = ic.getWordRangeAtCursor(" ", 1);
-        assertTrue(TextUtils.equals("word word", r.mWord));
-        ic.endBatchEdit();
-
-        // tab character instead of space
-        mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
-        ic.beginBatchEdit();
-        r = ic.getWordRangeAtCursor("\t", 1);
-        ic.endBatchEdit();
-        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();
-        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();
-        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();
-        assertTrue(TextUtils.equals("one word\tword", r.mWord));
-
-        // splitting on supplementary character
-        final String supplementaryChar = "\uD840\uDC8A";
-        mockInputMethodService.setInputConnection(
-                new MockConnection("one word" + supplementaryChar + "wo", "rd", et));
-        ic.beginBatchEdit();
-        r = ic.getWordRangeAtCursor(supplementaryChar, 0);
-        ic.endBatchEdit();
-        assertTrue(TextUtils.equals("word", r.mWord));
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
deleted file mode 100644
index abfaf30..0000000
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.inputmethod.InputMethodSubtype;
-
-import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
-
-import java.util.ArrayList;
-import java.util.Locale;
-
-@SmallTest
-public class SubtypeLocaleTests extends AndroidTestCase {
-    // Locale to subtypes list.
-    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
-
-    private RichInputMethodManager mRichImm;
-    private Resources mRes;
-
-    InputMethodSubtype EN_US;
-    InputMethodSubtype EN_GB;
-    InputMethodSubtype ES_US;
-    InputMethodSubtype FR;
-    InputMethodSubtype FR_CA;
-    InputMethodSubtype DE;
-    InputMethodSubtype ZZ;
-    InputMethodSubtype DE_QWERTY;
-    InputMethodSubtype FR_QWERTZ;
-    InputMethodSubtype EN_US_AZERTY;
-    InputMethodSubtype EN_UK_DVORAK;
-    InputMethodSubtype ES_US_COLEMAK;
-    InputMethodSubtype ZZ_PC;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        final Context context = getContext();
-        RichInputMethodManager.init(context);
-        mRichImm = RichInputMethodManager.getInstance();
-        mRes = context.getResources();
-        SubtypeLocale.init(context);
-
-        EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.US.toString(), "qwerty");
-        EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.UK.toString(), "qwerty");
-        ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                "es_US", "spanish");
-        FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.FRENCH.toString(), "azerty");
-        FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.CANADA_FRENCH.toString(), "qwerty");
-        DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                Locale.GERMAN.toString(), "qwertz");
-        ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
-                SubtypeLocale.NO_LANGUAGE, "qwerty");
-        DE_QWERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.GERMAN.toString(), "qwerty", null);
-        FR_QWERTZ = AdditionalSubtype.createAdditionalSubtype(
-                Locale.FRENCH.toString(), "qwertz", null);
-        EN_US_AZERTY = AdditionalSubtype.createAdditionalSubtype(
-                Locale.US.toString(), "azerty", null);
-        EN_UK_DVORAK = AdditionalSubtype.createAdditionalSubtype(
-                Locale.UK.toString(), "dvorak", null);
-        ES_US_COLEMAK = AdditionalSubtype.createAdditionalSubtype(
-                "es_US", "colemak", null);
-        ZZ_PC = AdditionalSubtype.createAdditionalSubtype(
-                SubtypeLocale.NO_LANGUAGE, "pcqwerty", null);
-
-    }
-
-    public void testAllFullDisplayName() {
-        for (final InputMethodSubtype subtype : mSubtypesList) {
-            final String subtypeName = SubtypeLocale.getSubtypeDisplayName(subtype);
-            if (SubtypeLocale.isNoLanguage(subtype)) {
-                final String noLanguage = mRes.getString(R.string.subtype_no_language);
-                assertTrue(subtypeName, subtypeName.contains(noLanguage));
-            } else {
-                final String languageName =
-                        SubtypeLocale.getSubtypeLocaleDisplayName(subtype.getLocale());
-                assertTrue(subtypeName, subtypeName.contains(languageName));
-            }
-        }
-    }
-
-    // InputMethodSubtype's display name in its locale.
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  display name
-    // ------ ------- - ----------------------
-    //  en_US qwerty  F  English (US)            exception
-    //  en_GB qwerty  F  English (UK)            exception
-    //  es_US spanish F  Español (EE.UU.)        exception
-    //  fr    azerty  F  Français
-    //  fr_CA qwerty  F  Français (Canada)
-    //  de    qwertz  F  Deutsch
-    //  zz    qwerty  F  No language (QWERTY)    in system locale
-    //  fr    qwertz  T  Français (QWERTZ)
-    //  de    qwerty  T  Deutsch (QWERTY)
-    //  en_US azerty  T  English (US) (AZERTY)   exception
-    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
-    //  es_US colemak T  Español (EE.UU.) (Colemak)  exception
-    //  zz    pc      T  No language (PC)        in system locale
-
-    public void testPredefinedSubtypesInEnglish() {
-        assertEquals("en_US", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_US));
-        assertEquals("en_GB", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_GB));
-        assertEquals("es_US", "spanish", SubtypeLocale.getKeyboardLayoutSetName(ES_US));
-        assertEquals("fr   ", "azerty", SubtypeLocale.getKeyboardLayoutSetName(FR));
-        assertEquals("fr_CA", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(FR_CA));
-        assertEquals("de   ", "qwertz", SubtypeLocale.getKeyboardLayoutSetName(DE));
-        assertEquals("zz   ", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(ZZ));
-
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("en_US", "English (US)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_US));
-                assertEquals("en_GB", "English (UK)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_GB));
-                assertEquals("es_US", "Español (EE.UU.)",
-                        SubtypeLocale.getSubtypeDisplayName(ES_US));
-                assertEquals("fr   ", "Français",
-                        SubtypeLocale.getSubtypeDisplayName(FR));
-                assertEquals("fr_CA", "Français (Canada)",
-                        SubtypeLocale.getSubtypeDisplayName(FR_CA));
-                assertEquals("de   ", "Deutsch",
-                        SubtypeLocale.getSubtypeDisplayName(DE));
-                assertEquals("zz   ", "No language (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testAdditionalSubtypesInEnglish() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("fr qwertz",    "Français (QWERTZ)",
-                        SubtypeLocale.getSubtypeDisplayName(FR_QWERTZ));
-                assertEquals("de qwerty",    "Deutsch (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(DE_QWERTY));
-                assertEquals("en_US azerty", "English (US) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_US_AZERTY));
-                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Español (EE.UU.) (Colemak)",
-                        SubtypeLocale.getSubtypeDisplayName(ES_US_COLEMAK));
-                assertEquals("zz pc",        "No language (PC)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ_PC));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testPredefinedSubtypesInFrench() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("en_US", "English (US)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_US));
-                assertEquals("en_GB", "English (UK)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_GB));
-                assertEquals("es_US", "Español (EE.UU.)",
-                        SubtypeLocale.getSubtypeDisplayName(ES_US));
-                assertEquals("fr   ", "Français",
-                        SubtypeLocale.getSubtypeDisplayName(FR));
-                assertEquals("fr_CA", "Français (Canada)",
-                        SubtypeLocale.getSubtypeDisplayName(FR_CA));
-                assertEquals("de   ", "Deutsch",
-                        SubtypeLocale.getSubtypeDisplayName(DE));
-                assertEquals("zz   ", "Aucune langue (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.FRENCH);
-    }
-
-    public void testAdditionalSubtypesInFrench() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("fr qwertz",    "Français (QWERTZ)",
-                        SubtypeLocale.getSubtypeDisplayName(FR_QWERTZ));
-                assertEquals("de qwerty",    "Deutsch (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(DE_QWERTY));
-                assertEquals("en_US azerty", "English (US) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_US_AZERTY));
-                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
-                        SubtypeLocale.getSubtypeDisplayName(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Español (EE.UU.) (Colemak)",
-                        SubtypeLocale.getSubtypeDisplayName(ES_US_COLEMAK));
-                assertEquals("zz azerty",    "Aucune langue (PC)",
-                        SubtypeLocale.getSubtypeDisplayName(ZZ_PC));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.FRENCH);
-    }
-
-    // InputMethodSubtype's display name in system locale (en_US).
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  display name
-    // ------ ------- - ----------------------
-    //  en_US qwerty  F  English (US)            exception
-    //  en_GB qwerty  F  English (UK)            exception
-    //  es_US spanish F  Spanish (US)            exception
-    //  fr    azerty  F  French
-    //  fr_CA qwerty  F  French (Canada)
-    //  de    qwertz  F  German
-    //  zz    qwerty  F  No language (QWERTY)
-    //  fr    qwertz  T  French (QWERTZ)
-    //  de    qwerty  T  German (QWERTY)
-    //  en_US azerty  T  English (US) (AZERTY)   exception
-    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
-    //  es_US colemak T  Spanish (US) (Colemak)  exception
-    //  zz    pc      T  No language (PC)
-
-    public void testPredefinedSubtypesInEnglishSystemLocale() {
-        assertEquals("en_US", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_US));
-        assertEquals("en_GB", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_GB));
-        assertEquals("es_US", "spanish", SubtypeLocale.getKeyboardLayoutSetName(ES_US));
-        assertEquals("fr   ", "azerty", SubtypeLocale.getKeyboardLayoutSetName(FR));
-        assertEquals("fr_CA", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(FR_CA));
-        assertEquals("de   ", "qwertz", SubtypeLocale.getKeyboardLayoutSetName(DE));
-        assertEquals("zz   ", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(ZZ));
-
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("en_US", "English (US)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US));
-                assertEquals("en_GB", "English (UK)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_GB));
-                assertEquals("es_US", "Spanish (US)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US));
-                assertEquals("fr   ", "French",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR));
-                assertEquals("fr_CA", "French (Canada)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_CA));
-                assertEquals("de   ", "German",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE));
-                assertEquals("zz   ", "No language (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    public void testAdditionalSubtypesInEnglishSystemLocale() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("fr qwertz",    "French (QWERTZ)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
-                assertEquals("de qwerty",    "German (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
-                assertEquals("en_US azerty", "English (US) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
-                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Spanish (US) (Colemak)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
-                assertEquals("zz azerty",    "No language (PC)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.ENGLISH);
-    }
-
-    // InputMethodSubtype's display name in system locale (fr).
-    //        isAdditionalSubtype (T=true, F=false)
-    // locale layout  |  display name
-    // ------ ------- - ----------------------
-    //  en_US qwerty  F  Anglais (États-Unis)            exception
-    //  en_GB qwerty  F  Anglais (Royaume-Uni)            exception
-    //  es_US spanish F  Espagnol (États-Unis)            exception
-    //  fr    azerty  F  Français
-    //  fr_CA qwerty  F  Français (Canada)
-    //  de    qwertz  F  Allemand
-    //  zz    qwerty  F  Aucune langue (QWERTY)
-    //  fr    qwertz  T  Français (QWERTZ)
-    //  de    qwerty  T  Allemand (QWERTY)
-    //  en_US azerty  T  Anglais (États-Unis) (AZERTY)   exception
-    //  en_UK dvorak  T  Anglais (Royaume-Uni) (Dvorak)   exception
-    //  es_US colemak T  Espagnol (États-Unis) (Colemak)  exception
-    //  zz    pc      T  Aucune langue (PC)
-
-    public void testPredefinedSubtypesInFrenchSystemLocale() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("en_US", "Anglais (États-Unis)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US));
-                assertEquals("en_GB", "Anglais (Royaume-Uni)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_GB));
-                assertEquals("es_US", "Espagnol (États-Unis)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US));
-                assertEquals("fr   ", "Français",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR));
-                assertEquals("fr_CA", "Français (Canada)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_CA));
-                assertEquals("de   ", "Allemand",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE));
-                assertEquals("zz   ", "Aucune langue (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.FRENCH);
-    }
-
-    public void testAdditionalSubtypesInFrenchSystemLocale() {
-        final RunInLocale<Void> tests = new RunInLocale<Void>() {
-            @Override
-            protected Void job(Resources res) {
-                assertEquals("fr qwertz",    "Français (QWERTZ)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
-                assertEquals("de qwerty",    "Allemand (QWERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
-                assertEquals("en_US azerty", "Anglais (États-Unis) (AZERTY)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
-                assertEquals("en_UK dvorak", "Anglais (Royaume-Uni) (Dvorak)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
-                assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
-                assertEquals("zz azerty",    "Aucune langue (PC)",
-                        SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
-                return null;
-            }
-        };
-        tests.runInLocale(mRes, Locale.FRENCH);
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
index 9162522..8d0fe01 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestedWordsTests.java
@@ -21,6 +21,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
 import java.util.ArrayList;
 import java.util.Locale;
 
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
deleted file mode 100644
index 594ba0e..0000000
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-/**
- * Unit tests for UserHistoryDictionary
- */
-@LargeTest
-public class UserHistoryDictionaryTests extends AndroidTestCase {
-    private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName();
-    private SharedPreferences mPrefs;
-
-    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"
-    };
-
-    @Override
-    public void setUp() {
-        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
-    }
-
-    /**
-     * Generates a random word.
-     */
-    private String generateWord(final int value) {
-        final int lengthOfChars = CHARACTERS.length;
-        StringBuilder builder = new StringBuilder();
-        long lvalue = Math.abs((long)value);
-        while (lvalue > 0) {
-            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
-            lvalue /= lengthOfChars;
-        }
-        return builder.toString();
-    }
-
-    private List<String> generateWords(final int number, final Random random) {
-        final Set<String> wordSet = CollectionUtils.newHashSet();
-        while (wordSet.size() < number) {
-            wordSet.add(generateWord(random.nextInt()));
-        }
-        return new ArrayList<String>(wordSet);
-    }
-
-    private void addToDict(final UserHistoryDictionary dict, final List<String> words) {
-        String prevWord = null;
-        for (String word : words) {
-            dict.forceAddWordForTest(prevWord, word, true);
-            prevWord = word;
-        }
-    }
-
-    public void testRandomWords() {
-        File dictFile = null;
-        try {
-            Log.d(TAG, "This test can be used for profiling.");
-            Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true.");
-            final int numberOfWords = 1000;
-            final Random random = new Random(123456);
-            List<String> words = generateWords(numberOfWords, random);
-
-            final String locale = "testRandomWords";
-            final String fileName = "UserHistoryDictionary." + locale + ".dict";
-            dictFile = new File(getContext().getFilesDir(), fileName);
-            final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(),
-                    locale, mPrefs);
-            dict.isTest = true;
-
-            addToDict(dict, words);
-
-            try {
-                Log.d(TAG, "waiting for adding the word ...");
-                Thread.sleep(2000);
-            } catch (InterruptedException e) {
-                Log.d(TAG, "InterruptedException: " + e);
-            }
-
-            // write to file
-            dict.close();
-
-            try {
-                Log.d(TAG, "waiting for writing ...");
-                Thread.sleep(5000);
-            } catch (InterruptedException e) {
-                Log.d(TAG, "InterruptedException: " + e);
-            }
-        } finally {
-            if (dictFile != null) {
-                dictFile.delete();
-            }
-        }
-    }
-}
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
new file mode 100644
index 0000000..1434c6b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -0,0 +1,93 @@
+/*
+ * 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.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for WordComposer.
+ */
+@SmallTest
+public class WordComposerTests extends AndroidTestCase {
+    public void testMoveCursor() {
+        final WordComposer wc = new WordComposer();
+        final String STR_WITHIN_BMP = "abcdef";
+        wc.setComposingWord(STR_WITHIN_BMP, null);
+        assertEquals(wc.size(),
+                STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        wc.setCursorPositionWithinWord(2);
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor to after the 'd'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(2));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor to after the 'e'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertEquals(wc.size(), 6);
+        // Move the cursor to after the 'f'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor past the end of the word
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
+
+        // \uD861\uDED7 is 𨛗, a character outside the BMP
+        final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0,
+                        STR_WITH_SUPPLEMENTARY_CHAR.length()));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(6));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(2);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index b704d08..ef4ed33 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -22,13 +22,13 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.inputmethod.latin.CollectionUtils;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.ByteArrayWrapper;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -51,7 +51,8 @@
 @LargeTest
 public class BinaryDictIOTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOTests.class.getSimpleName();
-    private static final int MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_CODE_POINT_SET_SIZE = 50;
     private static final int UNIGRAM_FREQ = 10;
     private static final int BIGRAM_FREQ = 50;
     private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
@@ -73,13 +74,16 @@
             new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
 
     public BinaryDictIOTests() {
-        super();
+        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
+    }
 
-        final long time = System.currentTimeMillis();
-        Log.e(TAG, "Testing dictionary: seed is " + time);
-        final Random random = new Random(time);
+    public BinaryDictIOTests(final long seed, final int maxUnigrams) {
+        super();
+        Log.e(TAG, "Testing dictionary: seed is " + seed);
+        final Random random = new Random(seed);
         sWords.clear();
-        generateWords(MAX_UNIGRAMS, random);
+        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
+        generateWords(maxUnigrams, random, codePointSet);
 
         for (int i = 0; i < sWords.size(); ++i) {
             sChainBigrams.put(i, new ArrayList<Integer>());
@@ -94,6 +98,23 @@
         }
     }
 
+    private int[] generateCodePointSet(final int codePointSetSize, final Random random) {
+        final int[] codePointSet = new int[codePointSetSize];
+        for (int i = codePointSet.length - 1; i >= 0; ) {
+            final int r = Math.abs(random.nextInt());
+            if (r < 0) continue;
+            // Don't insert 0~0x20, but insert any other code point.
+            // Code points are in the range 0~0x10FFFF.
+            final int candidateCodePoint = (int)(0x20 + r % (Character.MAX_CODE_POINT - 0x20));
+            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
+            if (candidateCodePoint >= Character.MIN_SURROGATE
+                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
+            codePointSet[i] = candidateCodePoint;
+            --i;
+        }
+        return codePointSet;
+    }
+
     // Utilities for test
 
     /**
@@ -106,7 +127,7 @@
             if (bufferType == USE_BYTE_ARRAY) {
                 final byte[] array = new byte[(int)file.length()];
                 inStream.read(array);
-                return new UserHistoryDictIOUtils.ByteArrayWrapper(array);
+                return new ByteArrayWrapper(array);
             } else if (bufferType == USE_BYTE_BUFFER){
                 final ByteBuffer buffer = inStream.getChannel().map(
                         FileChannel.MapMode.READ_ONLY, 0, file.length());
@@ -129,28 +150,29 @@
     /**
      * Generates a random word.
      */
-    private String generateWord(final Random random) {
-        StringBuilder builder = new StringBuilder("a");
-        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~0x20, but insert any other code point.
-            // Code points are in the range 0~0x10FFFF.
-            final int candidateCodePoint = (int)(0x20 + r % (Character.MAX_CODE_POINT - 0x20));
-            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
-            if (candidateCodePoint >= Character.MIN_SURROGATE
-                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
-            builder.appendCodePoint(candidateCodePoint);
-            --count;
+    private String generateWord(final Random random, final int[] codePointSet) {
+        StringBuilder builder = new StringBuilder();
+        // 8 * 4 = 32 chars max, but we do it the following way so as to bias the random toward
+        // longer words. This should be closer to natural language, and more importantly, it will
+        // exercise the algorithms in dicttool much more.
+        final int count = 1 + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5);
+        while (builder.length() < count) {
+            builder.appendCodePoint(codePointSet[Math.abs(random.nextInt()) % codePointSet.length]);
         }
         return builder.toString();
     }
 
-    private void generateWords(final int number, final Random random) {
+    private void generateWords(final int number, final Random random, final int[] codePointSet) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
-            wordSet.add(generateWord(random));
+            wordSet.add(generateWord(random, codePointSet));
         }
         sWords.addAll(wordSet);
     }
@@ -184,6 +206,14 @@
         }
     }
 
+//    The following is useful to dump the dictionary into a textual file, but it can't compile
+//    on-device, so it's commented out.
+//    private void dumpToCombinedFileForDebug(final FusionDictionary dict, final String filename)
+//            throws IOException {
+//        com.android.inputmethod.latin.dicttool.CombinedInputOutput.writeDictionaryCombined(
+//                new java.io.FileWriter(new File(filename)), dict);
+//    }
+
     private long timeWritingDictToFile(final File file, final FusionDictionary dict,
             final FormatSpec.FormatOptions formatOptions) {
 
@@ -193,6 +223,9 @@
             final FileOutputStream out = new FileOutputStream(file);
 
             now = System.currentTimeMillis();
+            // If you need to dump the dict to a textual file, uncomment the line below and the
+            // function above
+            // dumpToCombinedFileForDebug(file, "/tmp/foo");
             BinaryDictInputOutput.writeDictionaryBinary(out, dict, formatOptions);
             diff = System.currentTimeMillis() - now;
 
@@ -558,8 +591,9 @@
 
         // Test a word that isn't contained within the dictionary.
         final Random random = new Random((int)System.currentTimeMillis());
+        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
         for (int i = 0; i < 1000; ++i) {
-            final String word = generateWord(random);
+            final String word = generateWord(random, codePointSet);
             if (sWords.indexOf(word) != -1) continue;
             runGetTerminalPosition(buffer, word, i, false);
         }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index 47885f0..9331da4 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -21,12 +21,12 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-import com.android.inputmethod.latin.CollectionUtils;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.ByteBufferWrapper;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface;
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.BufferedOutputStream;
 import java.io.File;
@@ -40,27 +40,35 @@
 import java.util.Random;
 
 @LargeTest
-public class BinaryDictIOUtilsTests  extends AndroidTestCase {
+public class BinaryDictIOUtilsTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
     private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
             new FormatSpec.FormatOptions(3, true);
-    private static final int MAX_UNIGRAMS = 1500;
 
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
+    public static final int DEFAULT_MAX_UNIGRAMS = 1500;
+    private final int mMaxUnigrams;
 
     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",
         "\u00FC" /* ü */, "\u00E2" /* â */, "\u00F1" /* ñ */, // accented characters
         "\u4E9C" /* 亜 */, "\u4F0A" /* 伊 */, "\u5B87" /* 宇 */, // kanji
-        "\uD841\uDE28" /* 𠘨 */, "\uD840\uDC0B" /* 𠀋 */, "\uD861\uDeD7" /* 𨛗 */ // surrogate pair
+        "\uD841\uDE28" /* 𠘨 */, "\uD840\uDC0B" /* 𠀋 */, "\uD861\uDED7" /* 𨛗 */ // surrogate pair
     };
 
     public BinaryDictIOUtilsTests() {
+        // 1500 is the default max unigrams
+        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
+    }
+
+    public BinaryDictIOUtilsTests(final long seed, final int maxUnigrams) {
         super();
-        final Random random = new Random(123456);
+        Log.d(TAG, "Seed for test is " + seed + ", maxUnigrams is " + maxUnigrams);
+        mMaxUnigrams = maxUnigrams;
+        final Random random = new Random(seed);
         sWords.clear();
-        for (int i = 0; i < MAX_UNIGRAMS; ++i) {
+        for (int i = 0; i < maxUnigrams; ++i) {
             sWords.add(generateWord(random.nextInt()));
         }
     }
@@ -390,6 +398,6 @@
 
         Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms.");
-        Log.d(TAG, "avg = " + ((double)sum/MAX_UNIGRAMS/1000000) + " ms.");
+        Log.d(TAG, "avg = " + ((double)sum/mMaxUnigrams/1000000) + " ms.");
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
new file mode 100644
index 0000000..8f9ef1d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -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.
+ */
+
+package com.android.inputmethod.latin.personalization;
+
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Unit tests for UserHistoryDictionary
+ */
+@LargeTest
+public class UserHistoryDictionaryTests extends AndroidTestCase {
+    private static final String TAG = UserHistoryDictionaryTests.class.getSimpleName();
+    private SharedPreferences mPrefs;
+
+    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"
+    };
+
+    @Override
+    public void setUp() {
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+    }
+
+    /**
+     * Generates a random word.
+     */
+    private String generateWord(final int value) {
+        final int lengthOfChars = CHARACTERS.length;
+        StringBuilder builder = new StringBuilder();
+        long lvalue = Math.abs((long)value);
+        while (lvalue > 0) {
+            builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]);
+            lvalue /= lengthOfChars;
+        }
+        return builder.toString();
+    }
+
+    private List<String> generateWords(final int number, final Random random) {
+        final Set<String> wordSet = CollectionUtils.newHashSet();
+        while (wordSet.size() < number) {
+            wordSet.add(generateWord(random.nextInt()));
+        }
+        return new ArrayList<String>(wordSet);
+    }
+
+    private void addToDict(final UserHistoryPredictionDictionary dict, final List<String> words) {
+        String prevWord = null;
+        for (String word : words) {
+            dict.forceAddWordForTest(prevWord, word, true);
+            prevWord = word;
+        }
+    }
+
+    public void testRandomWords() {
+        File dictFile = null;
+        try {
+            Log.d(TAG, "This test can be used for profiling.");
+            Log.d(TAG, "Usage: please set UserHistoryDictionary.PROFILE_SAVE_RESTORE to true.");
+            final int numberOfWords = 1000;
+            final Random random = new Random(123456);
+            List<String> words = generateWords(numberOfWords, random);
+
+            final String locale = "testRandomWords";
+            final String fileName = "UserHistoryDictionary." + locale + ".dict";
+            dictFile = new File(getContext().getFilesDir(), fileName);
+            final UserHistoryPredictionDictionary dict =
+                    PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
+                            getContext(), locale, mPrefs);
+            dict.isTest = true;
+
+            addToDict(dict, words);
+
+            try {
+                Log.d(TAG, "waiting for adding the word ...");
+                Thread.sleep(2000);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+
+            // write to file
+            dict.close();
+
+            try {
+                Log.d(TAG, "waiting for writing ...");
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+        } finally {
+            if (dictFile != null) {
+                dictFile.delete();
+            }
+        }
+    }
+
+    public void testStressTestForSwitchingLanguagesAndAddingWords() {
+        final int numberOfLanguages = 2;
+        final int numberOfLanguageSwitching = 100;
+        final int numberOfWordsIntertedForEachLanguageSwitch = 100;
+
+        final File dictFiles[] = new File[numberOfLanguages];
+        try {
+            final Random random = new Random(123456);
+
+            // Create locales for this test.
+            String locales[] = new String[numberOfLanguages];
+            for (int i = 0; i < numberOfLanguages; i++) {
+                locales[i] = "testSwitchingLanguages" + i;
+                final String fileName = "UserHistoryDictionary." + locales[i] + ".dict";
+                dictFiles[i] = new File(getContext().getFilesDir(), fileName);
+            }
+
+            final long now = System.currentTimeMillis();
+
+            for (int i = 0; i < numberOfLanguageSwitching; i++) {
+                final int index = i % numberOfLanguages;
+                // Switch languages to locales[index].
+                final UserHistoryPredictionDictionary dict =
+                        PersonalizationDictionaryHelper.getUserHistoryPredictionDictionary(
+                                getContext(), locales[index], mPrefs);
+                final List<String> words = generateWords(
+                        numberOfWordsIntertedForEachLanguageSwitch, random);
+                // Add random words to the user history dictionary.
+                addToDict(dict, words);
+                // write to file
+                dict.close();
+            }
+
+            final long end = System.currentTimeMillis();
+            Log.d(TAG, "testStressTestForSwitchingLanguageAndAddingWords took "
+                    + (end - now) + " ms");
+            try {
+                Log.d(TAG, "waiting for writing ...");
+                Thread.sleep(5000);
+            } catch (InterruptedException e) {
+                Log.d(TAG, "InterruptedException: " + e);
+            }
+        } finally {
+            for (final File file : dictFiles) {
+                if (file != null) {
+                    file.delete();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java b/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java
new file mode 100644
index 0000000..b311f5d
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java
@@ -0,0 +1,225 @@
+/*
+ * 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.utils;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.LineNumberReader;
+import java.io.StringReader;
+
+@SmallTest
+public class Base64ReaderTests extends AndroidTestCase {
+    private static final String EMPTY_STRING = "";
+    private static final String INCOMPLETE_CHAR1 = "Q";
+    // Encode 'A'.
+    private static final String INCOMPLETE_CHAR2 = "QQ";
+    // Encode 'A', 'B'
+    private static final String INCOMPLETE_CHAR3 = "QUI";
+    // Encode 'A', 'B', 'C'
+    private static final String COMPLETE_CHAR4 = "QUJD";
+    private static final String ALL_BYTE_PATTERN =
+            "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj\n"
+            + "JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH\n"
+            + "SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr\n"
+            + "bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n"
+            + "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz\n"
+            + "tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX\n"
+            + "2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7\n"
+            + "/P3+/w==";
+
+    public void test0CharInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(EMPTY_STRING)));
+        try {
+            reader.readUint8();
+            fail("0 char");
+        } catch (final EOFException e) {
+            assertEquals("0 char", 0, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test1CharInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR1)));
+        try {
+            reader.readUint8();
+            fail("1 char");
+        } catch (final EOFException e) {
+            assertEquals("1 char", 0, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test2CharsInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR2)));
+        try {
+            final int v1 = reader.readUint8();
+            assertEquals("2 chars pos 0", 'A', v1);
+            reader.readUint8();
+            fail("2 chars");
+        } catch (final EOFException e) {
+            assertEquals("2 chars", 1, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test3CharsInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR3)));
+        try {
+            final int v1 = reader.readUint8();
+            assertEquals("3 chars pos 0", 'A', v1);
+            final int v2 = reader.readUint8();
+            assertEquals("3 chars pos 1", 'B', v2);
+            reader.readUint8();
+            fail("3 chars");
+        } catch (final EOFException e) {
+            assertEquals("3 chars", 2, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test4CharsInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(COMPLETE_CHAR4)));
+        try {
+            final int v1 = reader.readUint8();
+            assertEquals("4 chars pos 0", 'A', v1);
+            final int v2 = reader.readUint8();
+            assertEquals("4 chars pos 1", 'B', v2);
+            final int v3 = reader.readUint8();
+            assertEquals("4 chars pos 2", 'C', v3);
+            reader.readUint8();
+            fail("4 chars");
+        } catch (final EOFException e) {
+            assertEquals("4 chars", 3, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void testAllBytePatternInt8() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(ALL_BYTE_PATTERN)));
+        try {
+            for (int i = 0; i <= 0xff; i++) {
+                final int v = reader.readUint8();
+                assertEquals("value: all byte pattern: pos " + i, i, v);
+                assertEquals("count: all byte pattern: pos " + i, i + 1, reader.getByteCount());
+            }
+        } catch (final EOFException e) {
+            assertEquals("all byte pattern", 256, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test0CharInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(EMPTY_STRING)));
+        try {
+            reader.readInt16();
+            fail("0 char");
+        } catch (final EOFException e) {
+            assertEquals("0 char", 0, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test1CharInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR1)));
+        try {
+            reader.readInt16();
+            fail("1 char");
+        } catch (final EOFException e) {
+            assertEquals("1 char", 0, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test2CharsInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR2)));
+        try {
+            reader.readInt16();
+            fail("2 chars");
+        } catch (final EOFException e) {
+            assertEquals("2 chars", 1, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test3CharsInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(INCOMPLETE_CHAR3)));
+        try {
+            final short v1 = reader.readInt16();
+            assertEquals("3 chars pos 0", 'A' << 8 | 'B', v1);
+            reader.readInt16();
+            fail("3 chars");
+        } catch (final EOFException e) {
+            assertEquals("3 chars", 2, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void test4CharsInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(COMPLETE_CHAR4)));
+        try {
+            final short v1 = reader.readInt16();
+            assertEquals("4 chars pos 0", 'A' << 8 | 'B', v1);
+            reader.readInt16();
+            fail("4 chars");
+        } catch (final EOFException e) {
+            assertEquals("4 chars", 3, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+
+    public void testAllBytePatternInt16() {
+        final Base64Reader reader = new Base64Reader(
+                new LineNumberReader(new StringReader(ALL_BYTE_PATTERN)));
+        try {
+            for (int i = 0; i <= 0xff; i += 2) {
+                final short v = reader.readInt16();
+                final short expected = (short)(i << 8 | (i + 1));
+                assertEquals("value: all byte pattern: pos " + i, expected, v);
+                assertEquals("count: all byte pattern: pos " + i, i + 2, reader.getByteCount());
+            }
+        } catch (final EOFException e) {
+            assertEquals("all byte pattern", 256, reader.getByteCount());
+        } catch (final IOException e) {
+            fail("IOException: " + e);
+        }
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/CapsModeUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
similarity index 98%
rename from tests/src/com/android/inputmethod/latin/CapsModeUtilsTests.java
rename to tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
index 339791d..cf3bdd6 100644
--- a/tests/src/com/android/inputmethod/latin/CapsModeUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/CapsModeUtilsTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
diff --git a/tests/src/com/android/inputmethod/latin/utils/CsvUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/CsvUtilsTests.java
new file mode 100644
index 0000000..a0fa8fe
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/CsvUtilsTests.java
@@ -0,0 +1,424 @@
+/*
+ * 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.utils;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.inputmethod.latin.utils.CsvUtils.CsvParseException;
+
+import java.util.Arrays;
+
+@SmallTest
+public class CsvUtilsTests extends AndroidTestCase {
+    public void testUnescape() {
+        assertEquals("", CsvUtils.unescapeField(""));
+        assertEquals("text", CsvUtils.unescapeField("text")); // text
+        assertEquals("", CsvUtils.unescapeField("\"\"")); // ""
+        assertEquals("\"", CsvUtils.unescapeField("\"\"\"\"")); // """" -> "
+        assertEquals("text", CsvUtils.unescapeField("\"text\"")); // "text" -> text
+        assertEquals("\"text", CsvUtils.unescapeField("\"\"\"text\"")); // """text" -> "text
+        assertEquals("text\"", CsvUtils.unescapeField("\"text\"\"\"")); // "text""" -> text"
+        assertEquals("te\"xt", CsvUtils.unescapeField("\"te\"\"xt\"")); // "te""xt" -> te"xt
+        assertEquals("\"text\"",
+                CsvUtils.unescapeField("\"\"\"text\"\"\"")); // """text""" -> "text"
+        assertEquals("t\"e\"x\"t",
+                CsvUtils.unescapeField("\"t\"\"e\"\"x\"\"t\"")); // "t""e""x""t" -> t"e"x"t
+    }
+
+    public void testUnescapeException() {
+        try {
+            final String text = CsvUtils.unescapeField("\""); // "
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"\"\""); // """
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"\"\"\"\""); // """""
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"text"); // "text
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("text\""); // text"
+            fail("Raw quote in text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("te\"xt"); // te"xt
+            fail("Raw quote in text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"\"text"); // ""text
+            fail("Raw quote in quoted text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("text\"\""); // text""
+            fail("Escaped quote in text: text=" + text);
+        } catch (final CsvParseException success)  {
+            assertEquals("Escaped quote in text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("te\"\"xt"); // te""xt
+            fail("Escaped quote in text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Escaped quote in text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"\"text\""); // ""text"
+            fail("Raw quote in quoted text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"text\"\""); // "text""
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"te\"xt\""); // "te"xt"
+            fail("Raw quote in quoted text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\"b,c"); // "b,c
+            fail("Unterminated quote: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String text = CsvUtils.unescapeField("\",\"a\""); // ","a"
+            fail("Raw quote in quoted text: text=" + text);
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+    }
+
+    private static <T> void assertArrayEquals(final T[] expected, final T[] actual) {
+        if (expected == actual) {
+            return;
+        }
+        if (expected == null || actual == null) {
+            assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+            return;
+        }
+        if (expected.length != actual.length) {
+            assertEquals("[length]", Arrays.toString(expected), Arrays.toString(actual));
+            return;
+        }
+        for (int i = 0; i < expected.length; i++) {
+            final T e = expected[i];
+            final T a = actual[i];
+            if (e == a) {
+                continue;
+            }
+            assertEquals("["+i+"]", expected[i], actual[i]);
+        }
+    }
+
+    public void testSplit() {
+        assertArrayEquals(new String[]{""}, CsvUtils.split(""));
+        assertArrayEquals(new String[]{"  "}, CsvUtils.split("  "));
+        assertArrayEquals(new String[]{"text"}, CsvUtils.split("text"));
+        assertArrayEquals(new String[]{" a b "}, CsvUtils.split(" a b "));
+
+        assertArrayEquals(new String[]{"", ""}, CsvUtils.split(","));
+        assertArrayEquals(new String[]{"", "", ""}, CsvUtils.split(",,"));
+        assertArrayEquals(new String[]{" ", " "}, CsvUtils.split(" , "));
+        assertArrayEquals(new String[]{" ", " ", " "}, CsvUtils.split(" , , "));
+        assertArrayEquals(new String[]{"a", "b"}, CsvUtils.split("a,b"));
+        assertArrayEquals(new String[]{" a ", " b "}, CsvUtils.split(" a , b "));
+
+        assertArrayEquals(new String[]{"text"},
+                CsvUtils.split("\"text\"")); // "text"
+        assertArrayEquals(new String[]{" text "},
+                CsvUtils.split("\" text \"")); // "_text_"
+
+        assertArrayEquals(new String[]{""},
+                CsvUtils.split("\"\"")); // ""
+        assertArrayEquals(new String[]{"\""},
+                CsvUtils.split("\"\"\"\"")); // """"
+        assertArrayEquals(new String[]{"", ""},
+                CsvUtils.split("\"\",\"\"")); // "",""
+        assertArrayEquals(new String[]{"\",\""},
+                CsvUtils.split("\"\"\",\"\"\"")); // ""","""
+        assertArrayEquals(new String[]{"\"", "\""},
+                CsvUtils.split("\"\"\"\",\"\"\"\"")); // """",""""
+        assertArrayEquals(new String[]{"\"", "\",\""},
+                CsvUtils.split("\"\"\"\",\"\"\",\"\"\"")); // """",""","""
+        assertArrayEquals(new String[]{"\",\"", "\""},
+                CsvUtils.split("\"\"\",\"\"\",\"\"\"\"")); // """,""",""""
+
+        assertArrayEquals(new String[]{" a ", " b , c "},
+                CsvUtils.split(" a ,\" b , c \"")); // _a_,"_b_,_c_"
+        assertArrayEquals(new String[]{" a ", " b , c ", " d "},
+                CsvUtils.split(" a ,\" b , c \", d ")); // _a_,"_b_,_c_",_d_
+    }
+
+    public void testSplitException() {
+        try {
+            final String[] fields = CsvUtils.split(" \"text\" "); // _"text"_
+            fail("Raw quote in text: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in text", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split(" \" text \" "); // _"_text_"_
+            fail("Raw quote in text: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in text", success.getMessage());
+        }
+
+        try {
+            final String[] fields = CsvUtils.split("a,\"b,"); // a,",b
+            fail("Unterminated quote: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split("a,\"\"\",b"); // a,""",b
+            fail("Unterminated quote: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split("a,\"\"\"\"\",b"); // a,""""",b
+            fail("Unterminated quote: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split("a,\"b,c"); // a,"b,c
+            fail("Unterminated quote: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Unterminated quote", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split("a,\",\"b,c"); // a,","b,c
+            fail("Raw quote in quoted text: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+        try {
+            final String[] fields = CsvUtils.split("a,\",\"b\",\",c"); // a,","b",",c
+            fail("Raw quote in quoted text: fields=" + Arrays.toString(fields));
+        } catch (final CsvParseException success) {
+            assertEquals("Raw quote in quoted text", success.getMessage());
+        }
+    }
+
+    public void testSplitWithTrimSpaces() {
+        final int trimSpaces = CsvUtils.SPLIT_FLAGS_TRIM_SPACES;
+        assertArrayEquals(new String[]{""}, CsvUtils.split(trimSpaces, ""));
+        assertArrayEquals(new String[]{""}, CsvUtils.split(trimSpaces, "  "));
+        assertArrayEquals(new String[]{"text"}, CsvUtils.split(trimSpaces, "text"));
+        assertArrayEquals(new String[]{"a b"}, CsvUtils.split(trimSpaces, " a b "));
+
+        assertArrayEquals(new String[]{"", ""}, CsvUtils.split(trimSpaces, ","));
+        assertArrayEquals(new String[]{"", "", ""}, CsvUtils.split(trimSpaces, ",,"));
+        assertArrayEquals(new String[]{"", ""}, CsvUtils.split(trimSpaces, " , "));
+        assertArrayEquals(new String[]{"", "", ""}, CsvUtils.split(trimSpaces, " , , "));
+        assertArrayEquals(new String[]{"a", "b"}, CsvUtils.split(trimSpaces, "a,b"));
+        assertArrayEquals(new String[]{"a", "b"}, CsvUtils.split(trimSpaces, " a , b "));
+
+        assertArrayEquals(new String[]{"text"},
+                CsvUtils.split(trimSpaces, "\"text\"")); // "text"
+        assertArrayEquals(new String[]{"text"},
+                CsvUtils.split(trimSpaces, " \"text\" ")); // _"text"_
+        assertArrayEquals(new String[]{" text "},
+                CsvUtils.split(trimSpaces, "\" text \"")); // "_text_"
+        assertArrayEquals(new String[]{" text "},
+                CsvUtils.split(trimSpaces, " \" text \" ")); // _"_text_"_
+        assertArrayEquals(new String[]{"a", "b"},
+                CsvUtils.split(trimSpaces, " \"a\" , \"b\" ")); // _"a"_,_"b"_
+
+        assertArrayEquals(new String[]{""},
+                CsvUtils.split(trimSpaces, " \"\" ")); // _""_
+        assertArrayEquals(new String[]{"\""},
+                CsvUtils.split(trimSpaces, " \"\"\"\" ")); // _""""_
+        assertArrayEquals(new String[]{"", ""},
+                CsvUtils.split(trimSpaces, " \"\" , \"\" ")); // _""_,_""_
+        assertArrayEquals(new String[]{"\" , \""},
+                CsvUtils.split(trimSpaces, " \"\"\" , \"\"\" ")); // _"""_,_"""_
+        assertArrayEquals(new String[]{"\"", "\""},
+                CsvUtils.split(trimSpaces, " \"\"\"\" , \"\"\"\" ")); // _""""_,_""""_
+        assertArrayEquals(new String[]{"\"", "\" , \""},
+                CsvUtils.split(trimSpaces, " \"\"\"\" , \"\"\" , \"\"\" ")); // _""""_,_"""_,_"""_
+        assertArrayEquals(new String[]{"\" , \"", "\""},
+                CsvUtils.split(trimSpaces, " \"\"\" , \"\"\" , \"\"\"\" ")); // _"""_,_"""_,_""""_
+
+        assertArrayEquals(new String[]{"a", " b , c "},
+                CsvUtils.split(trimSpaces, " a , \" b , c \" ")); // _a_,_"_b_,_c_"_
+        assertArrayEquals(new String[]{"a", " b , c ", "d"},
+                CsvUtils.split(trimSpaces, " a, \" b , c \" , d ")); // _a,_"_b_,_c_"_,_d_
+    }
+
+    public void testEscape() {
+        assertEquals("", CsvUtils.escapeField("", false));
+        assertEquals("plain", CsvUtils.escapeField("plain", false));
+        assertEquals(" ", CsvUtils.escapeField(" ", false));
+        assertEquals("  ", CsvUtils.escapeField("  ", false));
+        assertEquals("a space", CsvUtils.escapeField("a space", false));
+        assertEquals(" space-at-start", CsvUtils.escapeField(" space-at-start", false));
+        assertEquals("space-at-end ", CsvUtils.escapeField("space-at-end ", false));
+        assertEquals("a lot of spaces", CsvUtils.escapeField("a lot of spaces", false));
+        assertEquals("\",\"", CsvUtils.escapeField(",", false));
+        assertEquals("\",,\"", CsvUtils.escapeField(",,", false));
+        assertEquals("\"a,comma\"", CsvUtils.escapeField("a,comma", false));
+        assertEquals("\",comma-at-begin\"", CsvUtils.escapeField(",comma-at-begin", false));
+        assertEquals("\"comma-at-end,\"", CsvUtils.escapeField("comma-at-end,", false));
+        assertEquals("\",,a,lot,,,of,commas,,\"",
+                CsvUtils.escapeField(",,a,lot,,,of,commas,,", false));
+        assertEquals("\"a comma,and a space\"", CsvUtils.escapeField("a comma,and a space", false));
+        assertEquals("\"\"\"\"", CsvUtils.escapeField("\"", false)); // " -> """"
+        assertEquals("\"\"\"\"\"\"", CsvUtils.escapeField("\"\"", false)); // "" -> """"""
+        assertEquals("\"\"\"\"\"\"\"\"", CsvUtils.escapeField("\"\"\"", false)); // """ -> """"""""
+        assertEquals("\"\"\"text\"\"\"",
+                CsvUtils.escapeField("\"text\"", false)); // "text" -> """text"""
+        assertEquals("\"text has \"\" in middle\"",
+                CsvUtils.escapeField("text has \" in middle", false));
+        assertEquals("\"\"\"quote,at begin\"", CsvUtils.escapeField("\"quote,at begin", false));
+        assertEquals("\"quote at,end\"\"\"", CsvUtils.escapeField("quote at,end\"", false));
+        assertEquals("\"\"\"quote at begin\"", CsvUtils.escapeField("\"quote at begin", false));
+        assertEquals("\"quote at end\"\"\"", CsvUtils.escapeField("quote at end\"", false));
+    }
+
+    public void testEscapeWithAlwaysQuoted() {
+        assertEquals("\"\"", CsvUtils.escapeField("", true));
+        assertEquals("\"plain\"", CsvUtils.escapeField("plain", true));
+        assertEquals("\" \"", CsvUtils.escapeField(" ", true));
+        assertEquals("\"  \"", CsvUtils.escapeField("  ", true));
+        assertEquals("\"a space\"", CsvUtils.escapeField("a space", true));
+        assertEquals("\" space-at-start\"", CsvUtils.escapeField(" space-at-start", true));
+        assertEquals("\"space-at-end \"", CsvUtils.escapeField("space-at-end ", true));
+        assertEquals("\"a lot of spaces\"", CsvUtils.escapeField("a lot of spaces", true));
+        assertEquals("\",\"", CsvUtils.escapeField(",", true));
+        assertEquals("\",,\"", CsvUtils.escapeField(",,", true));
+        assertEquals("\"a,comma\"", CsvUtils.escapeField("a,comma", true));
+        assertEquals("\",comma-at-begin\"", CsvUtils.escapeField(",comma-at-begin", true));
+        assertEquals("\"comma-at-end,\"", CsvUtils.escapeField("comma-at-end,", true));
+        assertEquals("\",,a,lot,,,of,commas,,\"",
+                CsvUtils.escapeField(",,a,lot,,,of,commas,,", true));
+        assertEquals("\"a comma,and a space\"", CsvUtils.escapeField("a comma,and a space", true));
+        assertEquals("\"\"\"\"", CsvUtils.escapeField("\"", true)); // " -> """"
+        assertEquals("\"\"\"\"\"\"", CsvUtils.escapeField("\"\"", true)); // "" -> """"""
+        assertEquals("\"\"\"\"\"\"\"\"", CsvUtils.escapeField("\"\"\"", true)); // """ -> """"""""
+        assertEquals("\"\"\"text\"\"\"",
+                CsvUtils.escapeField("\"text\"", true)); // "text" -> """text"""
+        assertEquals("\"text has \"\" in middle\"",
+                CsvUtils.escapeField("text has \" in middle", true));
+        assertEquals("\"\"\"quote,at begin\"", CsvUtils.escapeField("\"quote,at begin", true));
+        assertEquals("\"quote at,end\"\"\"", CsvUtils.escapeField("quote at,end\"", true));
+        assertEquals("\"\"\"quote at begin\"", CsvUtils.escapeField("\"quote at begin", true));
+        assertEquals("\"quote at end\"\"\"", CsvUtils.escapeField("quote at end\"", true));
+    }
+
+    public void testJoinWithoutColumnPositions() {
+        assertEquals("", CsvUtils.join());
+        assertEquals("", CsvUtils.join(""));
+        assertEquals(",", CsvUtils.join("", ""));
+
+        assertEquals("text, text,text ",
+                CsvUtils.join("text", " text", "text "));
+        assertEquals("\"\"\"\",\"\"\"\"\"\",\"\"\"text\"\"\"",
+                CsvUtils.join("\"", "\"\"", "\"text\""));
+        assertEquals("a b,\"c,d\",\"e\"\"f\"",
+                CsvUtils.join("a b", "c,d", "e\"f"));
+    }
+
+    public void testJoinWithoutColumnPositionsWithExtraSpace() {
+        final int extraSpace = CsvUtils.JOIN_FLAGS_EXTRA_SPACE;
+        assertEquals("", CsvUtils.join(extraSpace));
+        assertEquals("", CsvUtils.join(extraSpace, ""));
+        assertEquals(", ", CsvUtils.join(extraSpace, "", ""));
+
+        assertEquals("text,  text, text ",
+                CsvUtils.join(extraSpace, "text", " text", "text "));
+        // ","","text" -> """","""""","""text"""
+        assertEquals("\"\"\"\", \"\"\"\"\"\", \"\"\"text\"\"\"",
+                CsvUtils.join(extraSpace, "\"", "\"\"", "\"text\""));
+        assertEquals("a b, \"c,d\", \"e\"\"f\"",
+                CsvUtils.join(extraSpace, "a b", "c,d", "e\"f"));
+    }
+
+    public void testJoinWithoutColumnPositionsWithExtraSpaceAndAlwaysQuoted() {
+        final int extrSpaceAndQuoted =
+                CsvUtils.JOIN_FLAGS_EXTRA_SPACE | CsvUtils.JOIN_FLAGS_ALWAYS_QUOTED;
+        assertEquals("", CsvUtils.join(extrSpaceAndQuoted));
+        assertEquals("\"\"", CsvUtils.join(extrSpaceAndQuoted, ""));
+        assertEquals("\"\", \"\"", CsvUtils.join(extrSpaceAndQuoted, "", ""));
+
+        assertEquals("\"text\", \" text\", \"text \"",
+                CsvUtils.join(extrSpaceAndQuoted, "text", " text", "text "));
+        // ","","text" -> """", """""", """text"""
+        assertEquals("\"\"\"\", \"\"\"\"\"\", \"\"\"text\"\"\"",
+                CsvUtils.join(extrSpaceAndQuoted, "\"", "\"\"", "\"text\""));
+        assertEquals("\"a b\", \"c,d\", \"e\"\"f\"",
+                CsvUtils.join(extrSpaceAndQuoted, "a b", "c,d", "e\"f"));
+    }
+
+    public void testJoinWithColumnPositions() {
+        final int noFlags = CsvUtils.JOIN_FLAGS_NONE;
+        assertEquals("", CsvUtils.join(noFlags, new int[]{}));
+        assertEquals("   ", CsvUtils.join(noFlags, new int[]{3}, ""));
+        assertEquals(" ,", CsvUtils.join(noFlags, new int[]{1}, "", ""));
+        assertEquals(",  ", CsvUtils.join(noFlags, new int[]{0, 3}, "", ""));
+
+        assertEquals("text,    text, text ",
+                CsvUtils.join(noFlags, new int[]{0, 8, 15}, "text", " text", "text "));
+        // ","","text" -> """",   """""","""text"""
+        assertEquals("\"\"\"\",   \"\"\"\"\"\",\"\"\"text\"\"\"",
+                CsvUtils.join(noFlags, new int[]{0, 8, 15}, "\"", "\"\"", "\"text\""));
+        assertEquals("a b,    \"c,d\", \"e\"\"f\"",
+                CsvUtils.join(noFlags, new int[]{0, 8, 15}, "a b", "c,d", "e\"f"));
+    }
+
+    public void testJoinWithColumnPositionsWithExtraSpace() {
+        final int extraSpace = CsvUtils.JOIN_FLAGS_EXTRA_SPACE;
+        assertEquals("", CsvUtils.join(extraSpace, new int[]{}));
+        assertEquals("   ", CsvUtils.join(extraSpace, new int[]{3}, ""));
+        assertEquals(" , ", CsvUtils.join(extraSpace, new int[]{1}, "", ""));
+        assertEquals(",  ", CsvUtils.join(extraSpace, new int[]{0, 3}, "", ""));
+
+        assertEquals("text,    text, text ",
+                CsvUtils.join(extraSpace, new int[]{0, 8, 15}, "text", " text", "text "));
+        // ","","text" -> """",   """""", """text"""
+        assertEquals("\"\"\"\",   \"\"\"\"\"\", \"\"\"text\"\"\"",
+                CsvUtils.join(extraSpace, new int[]{0, 8, 15}, "\"", "\"\"", "\"text\""));
+        assertEquals("a b,    \"c,d\", \"e\"\"f\"",
+                CsvUtils.join(extraSpace, new int[]{0, 8, 15}, "a b", "c,d", "e\"f"));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java b/tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java
similarity index 97%
rename from tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java
rename to tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java
index 3259312..823bd5d 100644
--- a/tests/src/com/android/inputmethod/latin/ForgettingCurveTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/ForgettingCurveTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
diff --git a/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java b/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java
similarity index 99%
rename from tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java
rename to tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java
index 9d7203e..a520412 100644
--- a/tests/src/com/android/inputmethod/latin/RecapitalizeStatusTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/RecapitalizeStatusTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
diff --git a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
similarity index 91%
rename from tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java
rename to tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
index 2d1b836..cfff61e 100644
--- a/tests/src/com/android/inputmethod/latin/ResizableIntArrayTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/ResizableIntArrayTests.java
@@ -14,11 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.Arrays;
+
 @SmallTest
 public class ResizableIntArrayTests extends AndroidTestCase {
     private static final int DEFAULT_CAPACITY = 48;
@@ -186,7 +188,7 @@
         assertEquals("length after copy", dst.getLength(), src.getLength());
         assertSame("array after copy", array, dst.getPrimitiveArray());
         assertNotSame("array after copy", dst.getPrimitiveArray(), src.getPrimitiveArray());
-        assertArrayEquals("values after copy",
+        assertIntArrayEquals("values after copy",
                 dst.getPrimitiveArray(), 0, src.getPrimitiveArray(), 0, dst.getLength());
 
         final int smallerLength = DEFAULT_CAPACITY / 2;
@@ -197,7 +199,7 @@
         assertEquals("length after copy to smaller", dst.getLength(), src.getLength());
         assertNotSame("array after copy to smaller", array2, array3);
         assertNotSame("array after copy to smaller", array3, src.getPrimitiveArray());
-        assertArrayEquals("values after copy to smaller",
+        assertIntArrayEquals("values after copy to smaller",
                 dst.getPrimitiveArray(), 0, src.getPrimitiveArray(), 0, dst.getLength());
     }
 
@@ -220,7 +222,7 @@
         dst.append(src, 0, 0);
         assertEquals("length after append zero", dstLen, dst.getLength());
         assertSame("array after append zero", array, dst.getPrimitiveArray());
-        assertArrayEquals("values after append zero",
+        assertIntArrayEquals("values after append zero",
                 dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen);
 
         dst.append(src, 0, srcLen);
@@ -228,9 +230,9 @@
         assertSame("array after append", array, dst.getPrimitiveArray());
         assertTrue("primitive length after append",
                 dst.getPrimitiveArray().length >= dstLen + srcLen);
-        assertArrayEquals("original values after append",
+        assertIntArrayEquals("original values after append",
                 dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen);
-        assertArrayEquals("appended values after append",
+        assertIntArrayEquals("appended values after append",
                 src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen);
 
         dst.append(src, 0, srcLen);
@@ -238,11 +240,11 @@
         assertNotSame("array after 2nd append", array, dst.getPrimitiveArray());
         assertTrue("primitive length after 2nd append",
                 dst.getPrimitiveArray().length >= dstLen + srcLen * 2);
-        assertArrayEquals("original values after 2nd append",
+        assertIntArrayEquals("original values after 2nd append",
                 dstCopy.getPrimitiveArray(), 0, dst.getPrimitiveArray(), 0, dstLen);
-        assertArrayEquals("appended values after 2nd append",
+        assertIntArrayEquals("appended values after 2nd append",
                 src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen, srcLen);
-        assertArrayEquals("appended values after 2nd append",
+        assertIntArrayEquals("appended values after 2nd append",
                 src.getPrimitiveArray(), 0, dst.getPrimitiveArray(), dstLen + srcLen, srcLen);
     }
 
@@ -319,16 +321,22 @@
         }
     }
 
-    private static void assertArrayEquals(String message, int[] expecteds, int expectedPos,
-            int[] actuals, int actualPos, int length) {
-        if (expecteds == null && actuals == null) {
+    private static void assertIntArrayEquals(final String message, final int[] expecteds,
+            final int expectedPos, final int[] actuals, final int actualPos, final int length) {
+        if (expecteds == actuals) {
             return;
         }
         if (expecteds == null || actuals == null) {
-            fail(message + ": expecteds=" + expecteds + " actuals=" + actuals);
+            assertEquals(message, Arrays.toString(expecteds), Arrays.toString(actuals));
+            return;
+        }
+        if (expecteds.length < expectedPos + length || actuals.length < actualPos + length) {
+            fail(message + ": insufficient length: expecteds=" + Arrays.toString(expecteds)
+                    + " actuals=" + Arrays.toString(actuals));
+            return;
         }
         for (int i = 0; i < length; i++) {
-            assertEquals(message + ": element at " + i,
+            assertEquals(message + " [" + i + "]",
                     expecteds[i + expectedPos], actuals[i + actualPos]);
         }
     }
diff --git a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java
similarity index 68%
rename from tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
rename to tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java
index ed16846..1ae22e3 100644
--- a/tests/src/com/android/inputmethod/latin/ResourceUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/ResourceUtilsTests.java
@@ -14,30 +14,46 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.inputmethod.latin.utils.ResourceUtils.DeviceOverridePatternSyntaxError;
+
 import java.util.HashMap;
 
 @SmallTest
 public class ResourceUtilsTests extends AndroidTestCase {
     public void testFindDefaultConstant() {
         final String[] nullArray = null;
-        assertNull(ResourceUtils.findDefaultConstant(nullArray));
-
         final String[] emptyArray = {};
-        assertNull(ResourceUtils.findDefaultConstant(emptyArray));
-
         final String[] array = {
-            "HARDWARE=grouper,0.3",
-            "HARDWARE=mako,0.4",
-            ",defaultValue1",
-            "HARDWARE=manta,0.2",
-            ",defaultValue2",
+                "HARDWARE=grouper,0.3",
+                "HARDWARE=mako,0.4",
+                ",defaultValue1",
+                "HARDWARE=manta,0.2",
+                ",defaultValue2",
         };
-        assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1");
+
+        try {
+            assertNull(ResourceUtils.findDefaultConstant(nullArray));
+            assertNull(ResourceUtils.findDefaultConstant(emptyArray));
+            assertEquals(ResourceUtils.findDefaultConstant(array), "defaultValue1");
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            fail(e.getMessage());
+        }
+
+        final String[] errorArray = {
+            "HARDWARE=grouper,0.3",
+            "no_comma"
+        };
+        try {
+            final String defaultValue = ResourceUtils.findDefaultConstant(errorArray);
+            fail("exception should be thrown: defaultValue=" + defaultValue);
+        } catch (final DeviceOverridePatternSyntaxError e) {
+            assertEquals("Array element has no comma: no_comma", e.getMessage());
+        }
     }
 
     public void testFindConstantForKeyValuePairsSimple() {
@@ -67,33 +83,23 @@
 
         final HashMap<String,String> keyValues = CollectionUtils.newHashMap();
         keyValues.put(HARDWARE_KEY, "grouper");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mako");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "manta");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
-        try {
-            keyValues.clear();
-            keyValues.put("hardware", "grouper");
-            final String constant = ResourceUtils.findConstantForKeyValuePairs(keyValues, array);
-            fail("condition without HARDWARE must fail: constant=" + constant);
-        } catch (final RuntimeException e) {
-            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
-        }
+        keyValues.clear();
+        keyValues.put("hardware", "grouper");
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
+
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "MAKO");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mantaray");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
-        try {
-            final String constant = ResourceUtils.findConstantForKeyValuePairs(
-                    emptyKeyValue, array);
-            fail("emptyCondition shouldn't match: constant=" + constant);
-        } catch (final RuntimeException e) {
-            assertEquals(e.getMessage(), "Found unknown key: HARDWARE=grouper");
-        }
+        assertNull(ResourceUtils.findConstantForKeyValuePairs(emptyKeyValue, array));
     }
 
     public void testFindConstantForKeyValuePairsCombined() {
@@ -102,6 +108,8 @@
         final String MANUFACTURER_KEY = "MANUFACTURER";
         final String[] array = {
             ",defaultValue",
+            "no_comma",
+            "error_pattern,0.1",
             "HARDWARE=grouper:MANUFACTURER=asus,0.3",
             "HARDWARE=mako:MODEL=Nexus 4,0.4",
             "HARDWARE=manta:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
@@ -117,25 +125,25 @@
         keyValues.put(HARDWARE_KEY, "grouper");
         keyValues.put(MODEL_KEY, "Nexus 7");
         keyValues.put(MANUFACTURER_KEY, "asus");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "mako");
         keyValues.put(MODEL_KEY, "Nexus 4");
         keyValues.put(MANUFACTURER_KEY, "LGE");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "manta");
         keyValues.put(MODEL_KEY, "Nexus 10");
         keyValues.put(MANUFACTURER_KEY, "samsung");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
         keyValues.put(HARDWARE_KEY, "mantaray");
         assertNull(ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, failArray));
     }
 
     public void testFindConstantForKeyValuePairsRegexp() {
@@ -144,6 +152,8 @@
         final String MANUFACTURER_KEY = "MANUFACTURER";
         final String[] array = {
             ",defaultValue",
+            "no_comma",
+            "HARDWARE=error_regexp:MANUFACTURER=error[regexp,0.1",
             "HARDWARE=grouper|tilapia:MANUFACTURER=asus,0.3",
             "HARDWARE=[mM][aA][kK][oO]:MODEL=Nexus 4,0.4",
             "HARDWARE=manta.*:MODEL=Nexus 10:MANUFACTURER=samsung,0.2"
@@ -153,24 +163,24 @@
         keyValues.put(HARDWARE_KEY, "grouper");
         keyValues.put(MODEL_KEY, "Nexus 7");
         keyValues.put(MANUFACTURER_KEY, "asus");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "tilapia");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.3");
+        assertEquals("0.3", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "mako");
         keyValues.put(MODEL_KEY, "Nexus 4");
         keyValues.put(MANUFACTURER_KEY, "LGE");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "MAKO");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.4");
+        assertEquals("0.4", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
 
         keyValues.clear();
         keyValues.put(HARDWARE_KEY, "manta");
         keyValues.put(MODEL_KEY, "Nexus 10");
         keyValues.put(MANUFACTURER_KEY, "samsung");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
         keyValues.put(HARDWARE_KEY, "mantaray");
-        assertEquals(ResourceUtils.findConstantForKeyValuePairs(keyValues, array), "0.2");
+        assertEquals("0.2", ResourceUtils.findConstantForKeyValuePairs(keyValues, array));
     }
 }
diff --git a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
similarity index 81%
rename from tests/src/com/android/inputmethod/latin/StringUtilsTests.java
rename to tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
index 29e790a..9ee8e38 100644
--- a/tests/src/com/android/inputmethod/latin/StringUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/StringUtilsTests.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -40,57 +40,62 @@
         }));
     }
 
-    public void testContainsInCsv() {
-        assertFalse("null", StringUtils.containsInCsv("key", null));
-        assertFalse("empty", StringUtils.containsInCsv("key", ""));
-        assertFalse("not in 1 element", StringUtils.containsInCsv("key", "key1"));
-        assertFalse("not in 2 elements", StringUtils.containsInCsv("key", "key1,key2"));
+    public void testContainsInExtraValues() {
+        assertFalse("null", StringUtils.containsInCommaSplittableText("key", null));
+        assertFalse("empty", StringUtils.containsInCommaSplittableText("key", ""));
+        assertFalse("not in 1 element",
+                StringUtils.containsInCommaSplittableText("key", "key1"));
+        assertFalse("not in 2 elements",
+                StringUtils.containsInCommaSplittableText("key", "key1,key2"));
 
-        assertTrue("in 1 element", StringUtils.containsInCsv("key", "key"));
-        assertTrue("in 2 elements", StringUtils.containsInCsv("key", "key1,key"));
+        assertTrue("in 1 element", StringUtils.containsInCommaSplittableText("key", "key"));
+        assertTrue("in 2 elements", StringUtils.containsInCommaSplittableText("key", "key1,key"));
     }
 
-    public void testAppendToCsvIfNotExists() {
-        assertEquals("null", "key", StringUtils.appendToCsvIfNotExists("key", null));
-        assertEquals("empty", "key", StringUtils.appendToCsvIfNotExists("key", ""));
+    public void testAppendToExtraValuesIfNotExists() {
+        assertEquals("null", "key",
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", null));
+        assertEquals("empty", "key",
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", ""));
 
         assertEquals("not in 1 element", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key2"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "key",
-                StringUtils.appendToCsvIfNotExists("key", "key"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key,key2",
-                StringUtils.appendToCsvIfNotExists("key", "key,key2"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1,key",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key,key3",
-                StringUtils.appendToCsvIfNotExists("key", "key1,key,key3"));
+                StringUtils.appendToCommaSplittableTextIfNotExists("key", "key1,key,key3"));
     }
 
-    public void testRemoveFromCsvIfExists() {
-        assertEquals("null", "", StringUtils.removeFromCsvIfExists("key", null));
-        assertEquals("empty", "", StringUtils.removeFromCsvIfExists("key", ""));
+    public void testRemoveFromExtraValuesIfExists() {
+        assertEquals("null", "", StringUtils.removeFromCommaSplittableTextIfExists("key", null));
+        assertEquals("empty", "", StringUtils.removeFromCommaSplittableTextIfExists("key", ""));
 
         assertEquals("not in 1 element", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1"));
         assertEquals("not in 2 elements", "key1,key2",
-                StringUtils.removeFromCsvIfExists("key", "key1,key2"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key2"));
 
         assertEquals("in 1 element", "",
-                StringUtils.removeFromCsvIfExists("key", "key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key"));
         assertEquals("in 2 elements at position 1", "key2",
-                StringUtils.removeFromCsvIfExists("key", "key,key2"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key2"));
         assertEquals("in 2 elements at position 2", "key1",
-                StringUtils.removeFromCsvIfExists("key", "key1,key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key"));
         assertEquals("in 3 elements at position 2", "key1,key3",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key1,key,key3"));
 
         assertEquals("in 3 elements at position 1,2,3", "",
-                StringUtils.removeFromCsvIfExists("key", "key,key,key"));
+                StringUtils.removeFromCommaSplittableTextIfExists("key", "key,key,key"));
         assertEquals("in 5 elements at position 2,4", "key1,key3,key5",
-                StringUtils.removeFromCsvIfExists("key", "key1,key,key3,key,key5"));
+                StringUtils.removeFromCommaSplittableTextIfExists(
+                        "key", "key1,key,key3,key,key5"));
     }
 
 
diff --git a/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
new file mode 100644
index 0000000..e165850
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtilsTests.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+@SmallTest
+public class SubtypeLocaleUtilsTests extends AndroidTestCase {
+    // Locale to subtypes list.
+    private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList();
+
+    private RichInputMethodManager mRichImm;
+    private Resources mRes;
+
+    InputMethodSubtype EN_US;
+    InputMethodSubtype EN_GB;
+    InputMethodSubtype ES_US;
+    InputMethodSubtype FR;
+    InputMethodSubtype FR_CA;
+    InputMethodSubtype DE;
+    InputMethodSubtype ZZ;
+    InputMethodSubtype DE_QWERTY;
+    InputMethodSubtype FR_QWERTZ;
+    InputMethodSubtype EN_US_AZERTY;
+    InputMethodSubtype EN_UK_DVORAK;
+    InputMethodSubtype ES_US_COLEMAK;
+    InputMethodSubtype ZZ_AZERTY;
+    InputMethodSubtype ZZ_PC;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        final Context context = getContext();
+        RichInputMethodManager.init(context);
+        mRichImm = RichInputMethodManager.getInstance();
+        mRes = context.getResources();
+        SubtypeLocaleUtils.init(context);
+
+        EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.US.toString(), "qwerty");
+        EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.UK.toString(), "qwerty");
+        ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                "es_US", "spanish");
+        FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.FRENCH.toString(), "azerty");
+        FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.CANADA_FRENCH.toString(), "qwerty");
+        DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                Locale.GERMAN.toString(), "qwertz");
+        ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet(
+                SubtypeLocaleUtils.NO_LANGUAGE, "qwerty");
+        DE_QWERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.GERMAN.toString(), "qwerty", null);
+        FR_QWERTZ = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.FRENCH.toString(), "qwertz", null);
+        EN_US_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.US.toString(), "azerty", null);
+        EN_UK_DVORAK = AdditionalSubtypeUtils.createAdditionalSubtype(
+                Locale.UK.toString(), "dvorak", null);
+        ES_US_COLEMAK = AdditionalSubtypeUtils.createAdditionalSubtype(
+                "es_US", "colemak", null);
+        ZZ_AZERTY = AdditionalSubtypeUtils.createAdditionalSubtype(
+                SubtypeLocaleUtils.NO_LANGUAGE, "azerty", null);
+        ZZ_PC = AdditionalSubtypeUtils.createAdditionalSubtype(
+                SubtypeLocaleUtils.NO_LANGUAGE, "pcqwerty", null);
+
+    }
+
+    public void testAllFullDisplayName() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName =
+                    SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                final String noLanguage = mRes.getString(R.string.subtype_no_language);
+                assertTrue(subtypeName, subtypeName.contains(noLanguage));
+            } else {
+                final String languageName =
+                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
+                assertTrue(subtypeName, subtypeName.contains(languageName));
+            }
+        }
+    }
+
+    public void testKeyboardLayoutSetName() {
+        assertEquals("en_US", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_US));
+        assertEquals("en_GB", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(EN_GB));
+        assertEquals("es_US", "spanish", SubtypeLocaleUtils.getKeyboardLayoutSetName(ES_US));
+        assertEquals("fr   ", "azerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR));
+        assertEquals("fr_CA", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(FR_CA));
+        assertEquals("de   ", "qwertz", SubtypeLocaleUtils.getKeyboardLayoutSetName(DE));
+        assertEquals("zz   ", "qwerty", SubtypeLocaleUtils.getKeyboardLayoutSetName(ZZ));
+    }
+
+    // InputMethodSubtype's display name in system locale (en_US).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  en_US qwerty  F  English (US)            exception
+    //  en_GB qwerty  F  English (UK)            exception
+    //  es_US spanish F  Spanish (US)            exception
+    //  fr    azerty  F  French
+    //  fr_CA qwerty  F  French (Canada)
+    //  de    qwertz  F  German
+    //  zz    qwerty  F  No language (QWERTY)
+    //  fr    qwertz  T  French (QWERTZ)
+    //  de    qwerty  T  German (QWERTY)
+    //  en_US azerty  T  English (US) (AZERTY)   exception
+    //  en_UK dvorak  T  English (UK) (Dvorak)   exception
+    //  es_US colemak T  Spanish (US) (Colemak)  exception
+    //  zz    pc      T  No language (PC)
+
+    public void testPredefinedSubtypesInEnglishSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                assertEquals("en_US", "English (US)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US));
+                assertEquals("en_GB", "English (UK)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB));
+                assertEquals("es_US", "Spanish (US)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US));
+                assertEquals("fr   ", "French",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
+                assertEquals("fr_CA", "French (Canada)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("de   ", "German",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("zz   ", "No language (QWERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testAdditionalSubtypesInEnglishSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                assertEquals("fr qwertz",    "French (QWERTZ)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
+                assertEquals("de qwerty",    "German (QWERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
+                assertEquals("en_US azerty", "English (US) (AZERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "English (UK) (Dvorak)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Spanish (US) (Colemak)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
+                assertEquals("zz azerty",    "No language (PC)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    // InputMethodSubtype's display name in system locale (fr).
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  |  display name
+    // ------ ------- - ----------------------
+    //  en_US qwerty  F  Anglais (États-Unis)            exception
+    //  en_GB qwerty  F  Anglais (Royaume-Uni)            exception
+    //  es_US spanish F  Espagnol (États-Unis)            exception
+    //  fr    azerty  F  Français
+    //  fr_CA qwerty  F  Français (Canada)
+    //  de    qwertz  F  Allemand
+    //  zz    qwerty  F  Aucune langue (QWERTY)
+    //  fr    qwertz  T  Français (QWERTZ)
+    //  de    qwerty  T  Allemand (QWERTY)
+    //  en_US azerty  T  Anglais (États-Unis) (AZERTY)   exception
+    //  en_UK dvorak  T  Anglais (Royaume-Uni) (Dvorak)   exception
+    //  es_US colemak T  Espagnol (États-Unis) (Colemak)  exception
+    //  zz    pc      T  Aucune langue (PC)
+
+    public void testPredefinedSubtypesInFrenchSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                assertEquals("en_US", "Anglais (États-Unis)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US));
+                assertEquals("en_GB", "Anglais (Royaume-Uni)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_GB));
+                assertEquals("es_US", "Espagnol (États-Unis)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US));
+                assertEquals("fr   ", "Français",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR));
+                assertEquals("fr_CA", "Français (Canada)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_CA));
+                assertEquals("de   ", "Allemand",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE));
+                assertEquals("zz   ", "Aucune langue (QWERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    public void testAdditionalSubtypesInFrenchSystemLocale() {
+        final RunInLocale<Void> tests = new RunInLocale<Void>() {
+            @Override
+            protected Void job(final Resources res) {
+                assertEquals("fr qwertz",    "Français (QWERTZ)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(FR_QWERTZ));
+                assertEquals("de qwerty",    "Allemand (QWERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(DE_QWERTY));
+                assertEquals("en_US azerty", "Anglais (États-Unis) (AZERTY)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_US_AZERTY));
+                assertEquals("en_UK dvorak", "Anglais (Royaume-Uni) (Dvorak)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(EN_UK_DVORAK));
+                assertEquals("es_US colemak","Espagnol (États-Unis) (Colemak)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ES_US_COLEMAK));
+                assertEquals("zz azerty",    "Aucune langue (PC)",
+                        SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(ZZ_PC));
+                return null;
+            }
+        };
+        tests.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    public void testAllFullDisplayNameForSpacebar() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName =
+                    SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+            final String spacebarText = SubtypeLocaleUtils.getFullDisplayName(subtype);
+            final String languageName =
+                    SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale());
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                assertFalse(subtypeName, spacebarText.contains(languageName));
+            } else {
+                assertTrue(subtypeName, spacebarText.contains(languageName));
+            }
+        }
+    }
+
+   public void testAllMiddleDisplayNameForSpacebar() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName =
+                    SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+            final String spacebarText = SubtypeLocaleUtils.getMiddleDisplayName(subtype);
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                assertEquals(subtypeName,
+                        SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype), spacebarText);
+            } else {
+                assertEquals(subtypeName,
+                        SubtypeLocaleUtils.getSubtypeLocaleDisplayName(subtype.getLocale()),
+                        spacebarText);
+            }
+        }
+    }
+
+    public void testAllShortDisplayNameForSpacebar() {
+        for (final InputMethodSubtype subtype : mSubtypesList) {
+            final String subtypeName =
+                    SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
+            final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype);
+            final String spacebarText = SubtypeLocaleUtils.getShortDisplayName(subtype);
+            final String languageCode = StringUtils.capitalizeFirstCodePoint(
+                    locale.getLanguage(), locale);
+            if (SubtypeLocaleUtils.isNoLanguage(subtype)) {
+                assertEquals(subtypeName, "", spacebarText);
+            } else {
+                assertEquals(subtypeName, languageCode, spacebarText);
+            }
+        }
+    }
+
+    // InputMethodSubtype's display name for spacebar text in its locale.
+    //        isAdditionalSubtype (T=true, F=false)
+    // locale layout  | Short  Middle      Full
+    // ------ ------- - ---- --------- ----------------------
+    //  en_US qwerty  F  En  English   English (US)           exception
+    //  en_GB qwerty  F  En  English   English (UK)           exception
+    //  es_US spanish F  Es  Español   Español (EE.UU.)       exception
+    //  fr    azerty  F  Fr  Français  Français
+    //  fr_CA qwerty  F  Fr  Français  Français (Canada)
+    //  de    qwertz  F  De  Deutsch   Deutsch
+    //  zz    qwerty  F      QWERTY    QWERTY
+    //  fr    qwertz  T  Fr  Français  Français
+    //  de    qwerty  T  De  Deutsch   Deutsch
+    //  en_US azerty  T  En  English   English (US)
+    //  zz    azerty  T      AZERTY    AZERTY
+
+    private final RunInLocale<Void> testsPredefinedSubtypesForSpacebar = new RunInLocale<Void>() {
+        @Override
+        protected Void job(final Resources res) {
+            assertEquals("en_US", "English (US)", SubtypeLocaleUtils.getFullDisplayName(EN_US));
+            assertEquals("en_GB", "English (UK)", SubtypeLocaleUtils.getFullDisplayName(EN_GB));
+            assertEquals("es_US", "Español (EE.UU.)",
+                    SubtypeLocaleUtils.getFullDisplayName(ES_US));
+            assertEquals("fr   ", "Français",     SubtypeLocaleUtils.getFullDisplayName(FR));
+            assertEquals("fr_CA", "Français (Canada)",
+                    SubtypeLocaleUtils.getFullDisplayName(FR_CA));
+            assertEquals("de   ", "Deutsch",      SubtypeLocaleUtils.getFullDisplayName(DE));
+            assertEquals("zz   ", "QWERTY",       SubtypeLocaleUtils.getFullDisplayName(ZZ));
+
+            assertEquals("en_US", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_US));
+            assertEquals("en_GB", "English",  SubtypeLocaleUtils.getMiddleDisplayName(EN_GB));
+            assertEquals("es_US", "Español",  SubtypeLocaleUtils.getMiddleDisplayName(ES_US));
+            assertEquals("fr   ", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR));
+            assertEquals("fr_CA", "Français", SubtypeLocaleUtils.getMiddleDisplayName(FR_CA));
+            assertEquals("de   ", "Deutsch",  SubtypeLocaleUtils.getMiddleDisplayName(DE));
+            assertEquals("zz   ", "QWERTY",   SubtypeLocaleUtils.getMiddleDisplayName(ZZ));
+
+            assertEquals("en_US", "En", SubtypeLocaleUtils.getShortDisplayName(EN_US));
+            assertEquals("en_GB", "En", SubtypeLocaleUtils.getShortDisplayName(EN_GB));
+            assertEquals("es_US", "Es", SubtypeLocaleUtils.getShortDisplayName(ES_US));
+            assertEquals("fr   ", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR));
+            assertEquals("fr_CA", "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_CA));
+            assertEquals("de   ", "De", SubtypeLocaleUtils.getShortDisplayName(DE));
+            assertEquals("zz   ", "",   SubtypeLocaleUtils.getShortDisplayName(ZZ));
+            return null;
+        }
+    };
+
+    private final RunInLocale<Void> testsAdditionalSubtypesForSpacebar = new RunInLocale<Void>() {
+        @Override
+        protected Void job(final Resources res) {
+            assertEquals("fr qwertz",    "Français",
+                    SubtypeLocaleUtils.getFullDisplayName(FR_QWERTZ));
+            assertEquals("de qwerty",    "Deutsch",
+                    SubtypeLocaleUtils.getFullDisplayName(DE_QWERTY));
+            assertEquals("en_US azerty", "English (US)",
+                    SubtypeLocaleUtils.getFullDisplayName(EN_US_AZERTY));
+            assertEquals("zz azerty",    "AZERTY",
+                    SubtypeLocaleUtils.getFullDisplayName(ZZ_AZERTY));
+
+            assertEquals("fr qwertz",    "Français",
+                    SubtypeLocaleUtils.getMiddleDisplayName(FR_QWERTZ));
+            assertEquals("de qwerty",    "Deutsch",
+                    SubtypeLocaleUtils.getMiddleDisplayName(DE_QWERTY));
+            assertEquals("en_US azerty", "English",
+                    SubtypeLocaleUtils.getMiddleDisplayName(EN_US_AZERTY));
+            assertEquals("zz azerty",    "AZERTY",
+                    SubtypeLocaleUtils.getMiddleDisplayName(ZZ_AZERTY));
+
+            assertEquals("fr qwertz",    "Fr", SubtypeLocaleUtils.getShortDisplayName(FR_QWERTZ));
+            assertEquals("de qwerty",    "De", SubtypeLocaleUtils.getShortDisplayName(DE_QWERTY));
+            assertEquals("en_US azerty", "En",
+                    SubtypeLocaleUtils.getShortDisplayName(EN_US_AZERTY));
+            assertEquals("zz azerty",    "",   SubtypeLocaleUtils.getShortDisplayName(ZZ_AZERTY));
+            return null;
+        }
+    };
+
+    public void testPredefinedSubtypesForSpacebarInEnglish() {
+        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testAdditionalSubtypeForSpacebarInEnglish() {
+        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.ENGLISH);
+    }
+
+    public void testPredefinedSubtypesForSpacebarInFrench() {
+        testsPredefinedSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
+    }
+
+    public void testAdditionalSubtypeForSpacebarInFrench() {
+        testsAdditionalSubtypesForSpacebar.runInLocale(mRes, Locale.FRENCH);
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
similarity index 95%
rename from tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
rename to tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
index 211d012..b679839 100644
--- a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtilsTests.java
@@ -14,18 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.latin;
+package com.android.inputmethod.latin.utils;
 
 import android.content.Context;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface;
-import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener;
 import com.android.inputmethod.latin.makedict.FormatSpec;
 import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.personalization.UserHistoryDictionaryBigramList;
+import com.android.inputmethod.latin.utils.ByteArrayWrapper;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface;
+import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -152,8 +154,7 @@
             final byte[] buffer = new byte[(int)file.length()];
             inStream.read(buffer);
 
-            UserHistoryDictIOUtils.readDictionaryBinary(
-                    new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
+            UserHistoryDictIOUtils.readDictionaryBinary(new ByteArrayWrapper(buffer), listener);
         } catch (FileNotFoundException e) {
             Log.e(TAG, "file not found", e);
         } catch (IOException e) {
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index 666887a..d06be58 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -16,10 +16,19 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LATINIME_BASE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod
+BUILD_TOP := ../../../../..
+LATINIME_DIR := $(BUILD_TOP)/packages/inputmethods/LatinIME
+LATINIME_BASE_SOURCE_DIRECTORY := $(LATINIME_DIR)/java/src/com/android/inputmethod
 LATINIME_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin
 LATINIME_ANNOTATIONS_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/annotations
 MAKEDICT_CORE_SOURCE_DIRECTORY := $(LATINIME_CORE_SOURCE_DIRECTORY)/makedict
+DICTTOOL_COMPAT_TESTS_DIRECTORY := compat
+DICTTOOL_ONDEVICE_TESTS_DIRECTORY := \
+        $(LATINIME_DIR)/tests/src/com/android/inputmethod/latin/makedict/
+
+USED_TARGETTED_UTILS := \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/ByteArrayWrapper.java \
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/utils/CollectionUtils.java
 
 LOCAL_MAIN_SRC_FILES := $(call all-java-files-under, $(MAKEDICT_CORE_SOURCE_DIRECTORY))
 LOCAL_TOOL_SRC_FILES := $(call all-java-files-under, src)
@@ -28,12 +37,13 @@
 LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \
         $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
         $(LOCAL_ANNOTATIONS_SRC_FILES) \
-        $(LATINIME_CORE_SOURCE_DIRECTORY)/Constants.java
+        $(LATINIME_CORE_SOURCE_DIRECTORY)/Constants.java \
+        $(call all-java-files-under, tests) \
+        $(call all-java-files-under, $(DICTTOOL_ONDEVICE_TESTS_DIRECTORY)) \
+        $(call all-java-files-under, $(DICTTOOL_COMPAT_TESTS_DIRECTORY)) \
+        $(USED_TARGETTED_UTILS)
 
-ifeq ($(DICTTOOL_UNITTEST), true)
-    LOCAL_SRC_FILES += $(call all-java-files-under, tests)
-    LOCAL_JAVA_LIBRARIES := junit
-endif
+LOCAL_JAVA_LIBRARIES := junit
 
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool_aosp
diff --git a/tools/dicttool/compat/android/test/AndroidTestCase.java b/tools/dicttool/compat/android/test/AndroidTestCase.java
new file mode 100644
index 0000000..d01b7ad
--- /dev/null
+++ b/tools/dicttool/compat/android/test/AndroidTestCase.java
@@ -0,0 +1,35 @@
+/*
+ * 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 android.test;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+
+/**
+ * This is a compatibility class that aims at emulating android.test.AndroidTestcase from the
+ * Android library as simply as possible, and only to the extent that is used by the client classes.
+ * Its purpose is to provide compatibility without having to pull the whole Android library.
+ */
+public class AndroidTestCase extends TestCase {
+    public File getCacheDir() {
+        return new File(".");
+    }
+    public AndroidTestCase getContext() {
+        return this;
+    }
+}
diff --git a/tools/dicttool/compat/android/test/MoreAsserts.java b/tools/dicttool/compat/android/test/MoreAsserts.java
new file mode 100644
index 0000000..f56420b
--- /dev/null
+++ b/tools/dicttool/compat/android/test/MoreAsserts.java
@@ -0,0 +1,35 @@
+/*
+ * 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 android.test;
+
+import junit.framework.Assert;
+
+/**
+ * This is a compatibility class that aims at emulating android.test.MoreAsserts from the
+ * Android library as simply as possible, and only to the extent that is used by the client classes.
+ * Its purpose is to provide compatibility without having to pull the whole Android library.
+ */
+public class MoreAsserts {
+    public static void assertNotEqual(Object unexpected, Object actual) {
+        if (equal(unexpected, actual)) {
+            Assert.fail("expected not to be:<" + unexpected + ">");
+        }
+    }
+    private static boolean equal(Object a, Object b) {
+        return a == b || (a != null && a.equals(b));
+    }
+}
diff --git a/tools/dicttool/compat/android/test/suitebuilder/annotation/LargeTest.java b/tools/dicttool/compat/android/test/suitebuilder/annotation/LargeTest.java
new file mode 100644
index 0000000..ed00f8d
--- /dev/null
+++ b/tools/dicttool/compat/android/test/suitebuilder/annotation/LargeTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.test.suitebuilder.annotation;
+
+/**
+ * This is a compatibility class that aims at emulating the LargeTest annotation from the
+ * Android library as simply as possible, and only to the extent that is used by the client classes.
+ * Its purpose is to provide compatibility without having to pull the whole Android library.
+ */
+public @interface LargeTest {
+}
diff --git a/tools/dicttool/compat/android/util/Log.java b/tools/dicttool/compat/android/util/Log.java
new file mode 100644
index 0000000..d9df3a4
--- /dev/null
+++ b/tools/dicttool/compat/android/util/Log.java
@@ -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.
+ */
+
+package android.util;
+
+/**
+ * This is a compatibility class that aims at emulating android.util.Log from the
+ * Android library as simply as possible, and only to the extent that is used by the client classes.
+ * Its purpose is to provide compatibility without having to pull the whole Android library.
+ */
+public class Log {
+    public static void d(final String tag, final String message) {
+        System.out.println(tag + " : " + message);
+    }
+    public static void d(final String tag, final String message, final Exception e) {
+        System.out.println(tag + " : " + message + " : " + e);
+    }
+    public static void e(final String tag, final String message) {
+        d(tag, message);
+    }
+    public static void e(final String tag, final String message, final Exception e) {
+        e(tag, message, e);
+    }
+}
diff --git a/tools/dicttool/compat/android/util/SparseArray.java b/tools/dicttool/compat/android/util/SparseArray.java
new file mode 100644
index 0000000..6c76f19
--- /dev/null
+++ b/tools/dicttool/compat/android/util/SparseArray.java
@@ -0,0 +1,84 @@
+/*
+ * 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 android.util;
+
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class SparseArray<E> {
+    private final ArrayList<Integer> mKeys;
+    private final ArrayList<E> mValues;
+
+    public SparseArray() {
+        this(10);
+    }
+
+    public SparseArray(final int initialCapacity) {
+        mKeys = CollectionUtils.newArrayList(initialCapacity);
+        mValues = CollectionUtils.newArrayList(initialCapacity);
+    }
+
+    public int size() {
+        return mKeys.size();
+    }
+
+    public void clear() {
+        mKeys.clear();
+        mValues.clear();
+    }
+
+    public void put(final int key, final E value) {
+        final int index = Collections.binarySearch(mKeys, key);
+        if (index >= 0) {
+            mValues.set(index, value);
+            return;
+        }
+        final int insertIndex = ~index;
+        mKeys.add(insertIndex, key);
+        mValues.add(insertIndex, value);
+    }
+
+    public E get(final int key) {
+        return get(key, null);
+    }
+
+    public E get(final int key, final E valueIfKeyNotFound) {
+        final int index = Collections.binarySearch(mKeys, key);
+        if (index >= 0) {
+            return mValues.get(index);
+        }
+        return valueIfKeyNotFound;
+    }
+
+    public int indexOfKey(final int key) {
+        return mKeys.indexOf(key);
+    }
+
+    public int indexOfValue(final E value) {
+        return mValues.indexOf(value);
+    }
+
+    public int keyAt(final int index) {
+        return mKeys.get(index);
+    }
+
+    public E valueAt(final int index) {
+        return mValues.get(index);
+    }
+}
diff --git a/tools/dicttool/etc/dicttool_aosp b/tools/dicttool/etc/dicttool_aosp
index a4879a2..cc7111a 100755
--- a/tools/dicttool/etc/dicttool_aosp
+++ b/tools/dicttool/etc/dicttool_aosp
@@ -33,6 +33,7 @@
 prog="${progdir}"/`basename "${prog}"`
 cd "${oldwd}"
 
+classname=com.android.inputmethod.latin.dicttool.Dicttool
 jarfile=dicttool_aosp.jar
 frameworkdir="$progdir"
 if [ ! -r "$frameworkdir/$jarfile" ]
@@ -51,12 +52,21 @@
     exit 1
 fi
 
+lib=junit.jar
+if [ ! -r "$frameworkdir/$lib" ]
+then
+    echo `basename "$prog"`": can't find lib $lib"
+    exit 1
+fi
+
 if [ "$OSTYPE" = "cygwin" ] ; then
     jarpath=`cygpath -w  "$frameworkdir/$jarfile"`
+    libpath=`cygpath -w  "$frameworkdir/$lib"`
     progdir=`cygpath -w  "$progdir"`
 else
     jarpath="$frameworkdir/$jarfile"
+    libpath="$frameworkdir/$lib"
 fi
 
 # might need more memory, e.g. -Xmx128M
-exec java -ea -jar "$jarpath" "$@"
+exec java -ea -classpath "$libpath":"$jarpath" "$classname" "$@"
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
index 0e0095b..0d93c7f 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java
@@ -27,5 +27,6 @@
         Dicttool.addCommand("package", Package.Packager.class);
         Dicttool.addCommand("unpackage", Package.Unpackager.class);
         Dicttool.addCommand("makedict", Makedict.class);
+        Dicttool.addCommand("test", Test.class);
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java
index f899023..4612ae4 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java
@@ -40,10 +40,12 @@
         public Encrypter() {
         }
 
+        @Override
         public String getHelp() {
             return COMMAND + " <src_filename> <dst_filename>: Encrypts a file";
         }
 
+        @Override
         public void run() {
             throw new UnsupportedOperationException();
         }
@@ -55,10 +57,12 @@
         public Decrypter() {
         }
 
+        @Override
         public String getHelp() {
             return COMMAND + " <src_filename> <dst_filename>: Decrypts a file";
         }
 
+        @Override
         public void run() {
             throw new UnsupportedOperationException();
         }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
index 7b311c3..cacee52 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
@@ -72,15 +72,21 @@
         return command;
     }
 
-    private void execute(final String[] arguments) {
+    /**
+     * Executes the specified command with the specified arguments.
+     * @param arguments the arguments passed to dicttool.
+     * @return 0 for success, an error code otherwise (always 1 at the moment)
+     */
+    private int execute(final String[] arguments) {
         final Command command = getCommand(arguments);
         try {
             command.run();
+            return 0;
         } catch (Exception e) {
             System.out.println("Exception while processing command "
                     + command.getClass().getSimpleName() + " : " + e);
             e.printStackTrace();
-            return;
+            return 1;
         }
     }
 
@@ -89,6 +95,7 @@
             help();
             return;
         }
-        new Dicttool().execute(arguments);
+        // Exit with the success/error code from #execute() as status.
+        System.exit(new Dicttool().execute(arguments));
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
index b294807..9274dcd 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
@@ -22,9 +22,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
-import java.io.InputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 
 public class Package {
     private Package() {
@@ -39,10 +37,12 @@
         public Packager() {
         }
 
+        @Override
         public String getHelp() {
             return COMMAND + " <src_filename> <dst_filename>: Package a file for distribution";
         }
 
+        @Override
         public void run() throws IOException {
             if (mArgs.length != 2) {
                 throw new RuntimeException("Too many/too few arguments for command " + COMMAND);
@@ -67,11 +67,13 @@
         public Unpackager() {
         }
 
+        @Override
         public String getHelp() {
             return COMMAND + " <src_filename> <dst_filename>: Detects how a file is packaged and\n"
                     + "decrypts/uncompresses as necessary to produce a raw binary file.";
         }
 
+        @Override
         public void run() throws FileNotFoundException, IOException {
             if (mArgs.length != 2) {
                 throw new RuntimeException("Too many/too few arguments for command " + COMMAND);
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
new file mode 100644
index 0000000..972b6e7
--- /dev/null
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
@@ -0,0 +1,108 @@
+/**
+ * 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.dicttool;
+
+import com.android.inputmethod.latin.makedict.BinaryDictIOTests;
+import com.android.inputmethod.latin.makedict.BinaryDictIOUtilsTests;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest;
+import com.android.inputmethod.latin.makedict.FusionDictionaryTest;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+/**
+ * Dicttool command implementing self-tests.
+ */
+public class Test extends Dicttool.Command {
+    public static final String COMMAND = "test";
+    private long mSeed = System.currentTimeMillis();
+    private int mMaxUnigrams = BinaryDictIOUtilsTests.DEFAULT_MAX_UNIGRAMS;
+
+    private static final Class<?>[] sClassesToTest = {
+        BinaryDictOffdeviceUtilsTests.class,
+        FusionDictionaryTest.class,
+        BinaryDictInputOutputTest.class,
+        BinaryDictIOUtilsTests.class,
+        BinaryDictIOTests.class
+    };
+    private ArrayList<Method> mAllTestMethods = new ArrayList<Method>();
+    private ArrayList<String> mUsedTestMethods = new ArrayList<String>();
+
+    public Test() {
+        for (final Class<?> c : sClassesToTest) {
+            for (final Method m : c.getDeclaredMethods()) {
+                if (m.getName().startsWith("test") && Void.TYPE == m.getReturnType()
+                        && 0 == m.getParameterTypes().length) {
+                    mAllTestMethods.add(m);
+                }
+            }
+        }
+    }
+
+    @Override
+    public String getHelp() {
+        final StringBuilder s = new StringBuilder("test [-s seed] [-m maxUnigrams] [testName...]\n"
+                + "If seed is not specified, the current time is used.\nTest list is:\n");
+        for (final Method m : mAllTestMethods) {
+            s.append("  ");
+            s.append(m.getName());
+            s.append("\n");
+        }
+        return s.toString();
+    }
+
+    @Override
+    public void run() throws IllegalAccessException, InstantiationException,
+            InvocationTargetException {
+        int i = 0;
+        while (i < mArgs.length) {
+            final String arg = mArgs[i++];
+            if ("-s".equals(arg)) {
+                mSeed = Long.parseLong(mArgs[i++]);
+            } else if ("-m".equals(arg)) {
+                mMaxUnigrams = Integer.parseInt(mArgs[i++]);
+            } else {
+                mUsedTestMethods.add(arg);
+            }
+        }
+        runChosenTests();
+    }
+
+    private void runChosenTests() throws IllegalAccessException, InstantiationException,
+            InvocationTargetException {
+        for (final Method m : mAllTestMethods) {
+            final Class<?> declaringClass = m.getDeclaringClass();
+            if (!mUsedTestMethods.isEmpty() && !mUsedTestMethods.contains(m.getName())) continue;
+            // Some of the test classes expose a two-argument constructor, taking a long as a
+            // seed for Random, and an int for a vocabulary size to test the dictionary with. They
+            // correspond respectively to the -s and -m numerical arguments to this command, which
+            // are stored in mSeed and mMaxUnigrams. If the two-arguments constructor is present,
+            // then invoke it; otherwise, invoke the default constructor.
+            Constructor<?> twoArgsConstructor = null;
+            try {
+                twoArgsConstructor = declaringClass.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
+            } catch (NoSuchMethodException e) {
+                // No constructor with two args
+            }
+            final Object instance = null == twoArgsConstructor ? declaringClass.newInstance()
+                    : twoArgsConstructor.newInstance(mSeed, mMaxUnigrams);
+            m.invoke(instance);
+        }
+    }
+}
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
index fe3781d..7607113 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/FusionDictionaryTest.java
@@ -73,10 +73,6 @@
         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);
         }
     }
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index 2fc97b5..479a766 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
diff --git a/tools/maketext/res/values-az/donottranslate-more-keys.xml b/tools/maketext/res/values-az/donottranslate-more-keys.xml
new file mode 100644
index 0000000..db1784c
--- /dev/null
+++ b/tools/maketext/res/values-az/donottranslate-more-keys.xml
@@ -0,0 +1,59 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+    <string name="more_keys_for_a">&#x00E2;</string>
+    <!-- U+0259: "ə" LATIN SMALL LETTER SCHWA -->
+    <string name="more_keys_for_e">&#x0259;</string>
+    <!-- U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x0131;,&#x00EE;,&#x00EF;,&#x00EC;,&#x00ED;,&#x012F;,&#x012B;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F4;,&#x0153;,&#x00F2;,&#x00F3;,&#x00F5;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x015F;,&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x011F;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+</resources>
diff --git a/tools/maketext/res/values-kk/donottranslate-more-keys.xml b/tools/maketext/res/values-kk/donottranslate-more-keys.xml
new file mode 100644
index 0000000..0e953ff
--- /dev/null
+++ b/tools/maketext/res/values-kk/donottranslate-more-keys.xml
@@ -0,0 +1,57 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="keylabel_for_east_slavic_row1_12">&#x044A;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
+    <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0438;</string>
+    <!-- U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+         U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE -->
+    <string name="more_keys_for_cyrillic_u">&#x04AF;,&#x04B1;</string>
+    <!-- U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER -->
+    <string name="more_keys_for_cyrillic_ka">&#x049B;</string>
+    <!-- U+0451: "ё" CYRILLIC SMALL LETTER IO -->
+    <string name="more_keys_for_cyrillic_ie">&#x0451;</string>
+    <!-- U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER -->
+    <string name="more_keys_for_cyrillic_en">&#x04A3;</string>
+    <!-- U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE -->
+    <string name="more_keys_for_cyrillic_ghe">&#x0493;</string>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="more_keys_for_east_slavic_row2_1">&#x0456;</string>
+    <!-- U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA -->
+    <string name="more_keys_for_cyrillic_a">&#x04D9;</string>
+    <!-- U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O -->
+    <string name="more_keys_for_cyrillic_o">&#x04E9;</string>
+    <!-- U+04BB: "һ" CYRILLIC SMALL LETTER SHHA -->
+    <string name="more_keys_for_east_slavic_row2_11">&#x04BB;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
+    <!-- Label for "switch to alphabetic" key.
+         U+0410: "А" CYRILLIC CAPITAL LETTER A
+         U+0411: "Б" CYRILLIC CAPITAL LETTER BE
+         U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
+    <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+</resources>
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index b766b15..4cf2650 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -49,11 +49,14 @@
     <string name="keylabel_for_east_slavic_row2_11"></string>
     <string name="keylabel_for_east_slavic_row3_5"></string>
     <string name="more_keys_for_cyrillic_u"></string>
+    <string name="more_keys_for_cyrillic_ka"></string>
     <string name="more_keys_for_cyrillic_en"></string>
     <string name="more_keys_for_cyrillic_ghe"></string>
     <string name="more_keys_for_east_slavic_row2_1"></string>
+    <string name="more_keys_for_cyrillic_a"></string>
     <string name="more_keys_for_cyrillic_o"></string>
     <string name="more_keys_for_cyrillic_soft_sign"></string>
+    <string name="more_keys_for_east_slavic_row2_11"></string>
     <string name="keylabel_for_south_slavic_row1_6"></string>
     <string name="keylabel_for_south_slavic_row2_11"></string>
     <string name="keylabel_for_south_slavic_row3_1"></string>