Merge "Add a margin between "123" and the mic icon."
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index d680f18..548da1b 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -29,12 +29,20 @@
             </intent-filter>
             <meta-data android:name="android.view.textservice.scs" android:resource="@xml/spellchecker" />
         </service>
+
         <activity android:name="Settings" android:label="@string/english_ime_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
             </intent-filter>
         </activity>
 
+        <activity android:name="com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsActivity"
+                  android:label="@string/android_spell_checker_settings">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+
         <activity android:name="DebugSettings" android:label="@string/english_ime_debug_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/java/proguard.flags b/java/proguard.flags
index 3959500..33af890 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -27,6 +27,10 @@
   *;
 }
 
+-keep class com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment {
+  *;
+}
+
 -keep class com.android.inputmethod.latin.SettingsActivity {
   *;
 }
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index cbe6406..8d36328 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropsies"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibreer met sleuteldruk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Klank met sleuteldruk"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Opspring by druk van sleutel"</string>
@@ -81,8 +87,7 @@
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
     <skip />
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Meer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Laat wag"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wag"</string>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index 4d8ace5..16595d2 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"ግቤት አማራጮች"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"በቁልፍመጫንጊዜ አንዝር"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"በቁልፍ መጫን ላይ የሚወጣ ድምፅ"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"ቁልፍ ጫን ላይ ብቅ ባይ"</string>
@@ -81,8 +87,7 @@
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
     <skip />
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"ተጨማሪ"</string>
     <string name="label_pause_key" msgid="181098308428035340">"ላፍታ አቁም"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"ቆይ"</string>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 4330813..46f0c1d 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"لوحة مفاتيح Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"إعدادات لوحة مفاتيح Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"خيارات الإرسال"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحيح Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"اهتزاز عند الضغط على مفتاح"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"صوت عند الضغط على مفتاح"</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index cc958af..f2514ec 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Клавиатура на Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Настройки на клавиатурата на Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опции за въвеждане"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Корекция на Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Да вибрира при натискане на клавиш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натискане на клавиш"</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 2c9fbfb..6daca17 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Teclat Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configuració del teclat d\'Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcions d\'entrada"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correcció d\'Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibra en prémer tecles"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"So en prémer una tecla"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Envia"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Més"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
@@ -133,7 +137,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Micro en tecl. símb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Entr. veu desactiv."</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Selecciona el mètode d\'entrada"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"Configuració de mètodes d\'entrada"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Configura mètodes d\'entrada"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomes d\'entrada"</string>
     <string name="select_language" msgid="2573265881207142437">"Selecciona l\'idioma d\'entrada"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Torna a tocar per desar"</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 38417a0..9cd237e 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Klávesnice Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavení klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávání textu a dat"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy pravopisu Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Při stisku klávesy vibrovat"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk při stisku klávesy"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Odeslat"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Další"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Čekat"</string>
@@ -133,7 +137,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mikr. na kláv. se symb."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Hlasový vstup vypnut"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Výběr metody zadávání dat"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"Nakonfigurovat metody vstupu"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Konfigurace metod vstupu"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Vstupní jazyky"</string>
     <string name="select_language" msgid="2573265881207142437">"Výběr jazyků vstupu"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Dalším dotykem slovo uložíte"</string>
@@ -144,7 +148,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Klepnutím na zadaná slova tato slova opravíte, musí však být viditelné návrhy."</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Motiv klávesnice"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"němčina (QWERTY)"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"angličtina (VB)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"angličtina (Spojené království)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"angličtina (USA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Režim studie použitelnosti"</string>
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 97dc8a0..85cbb85 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android-tastatur"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Indstillinger for Android-tastatur"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Indstillinger for input"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-rettelse"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibration ved tastetryk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetryk"</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index fdce3bb..7a20f69 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Eingabeoptionen"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrieren b. Tastendruck"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ton bei Tastendruck"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up bei Tastendruck"</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 2629c24..e0c5033 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Πληκτρολόγιο Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Ρυθμίσεις πληκτρολογίου Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Επιλογές εισόδου"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Διόρθωση Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Δόνηση κατά το πάτημα πλήκτρων"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Ήχος κατά το πάτημα πλήκτρων"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Αποστολή"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ΑΒΓ"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">";123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Περισσότερα"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Παύση"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Αναμ."</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 7a0449c..f89c293 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android keyboard"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android keyboard settings"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Input options"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android correction"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrate on key-press"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sound on key-press"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Send"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"More"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wait"</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 4eacee9..87a15a1 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Teclado de Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configuración de teclado de Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones de entrada"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Corrector de Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar teclas"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonar al pulsar las teclas"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Más"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index aae38ca..c3b97c7 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opciones introducción texto"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar al pulsar tecla"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sonido al pulsar tecla"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup al pulsar tecla"</string>
@@ -64,8 +70,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Más"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Espera"</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index bacc180..5a085d8 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"صفحه کلید Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"تنظیمات صفحه کلید Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"گزینه های ورودی"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"تصحیح Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"لرزش با فشار کلید"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"صدا با فشار کلید"</string>
@@ -144,7 +149,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"فقط هنگامی که پیشنهادات قابل مشاهده هستند، برای تصحیح کلمات وارد شده آنها را لمس کنید"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"طرح زمینه صفحه کلید"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"آلمانی QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"انگیسی (انگلستان)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"انگیسی (UK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"انگیسی (US)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"حالت بررسی قابلیت استفاده"</string>
 </resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 1b45be7..88a8ea0 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Syöttövalinnat"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Käytä värinää näppäimiä painettaessa"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toista ääni näppäimiä painettaessa"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ponnahdusikkuna painalluksella"</string>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index e658da0..c35c9bb 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Clavier Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Paramètres du clavier Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Options de saisie"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correcteur Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer à chaque touche"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Son à chaque touche"</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 55d4b14..4d27d2c 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android tipkovnica"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Postavke tipkovnice za Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcije ulaza"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Ispravak za Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibracija pri pritisku na tipku"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri pritisku tipke"</string>
@@ -144,7 +149,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dodirnite unesene riječi da biste ih ispravili samo kada su prijedlozi vidljivi"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"njemački QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"engleski (Velika Britanija)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"engleski (SAD)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Engleski (UK)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Engleski (SAD)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način studije upotrebljivosti"</string>
 </resources>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index d682b82..afae17d 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android-billentyűzet"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android billentyűzetbeállítások"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Beviteli beállítások"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android korrekció"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rezgés billentyű megnyomása esetén"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Hangjelzés billentyű megnyomása esetén"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Küldés"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Egyebek"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Szün."</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Vár"</string>
@@ -144,7 +148,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"A beírt szavakat csak akkor javíthatja ki megérintve, ha látszanak javaslatok"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Billentyűzettéma"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"Német QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"angol (brit)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"angol (amerikai)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"angol (Egyesült Királyság)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"angol (Egyesült Államok)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Használhatósági teszt"</string>
 </resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index d8bb604..f350686 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Keyboard Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Setelan keyboard Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opsi masukan"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Koreksi android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar jika tombol ditekan"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Berbunyi jika tombol ditekan"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Kirimkan"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Lainnya"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Jeda"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Tunggu"</string>
@@ -144,7 +148,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Sentuh kata yang dimasukkan untuk memperbaikinya, hanya saat saran dapat dilihat"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema keyboard"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY Jerman"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"Inggris (UK)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Inggris (Inggris)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Inggris (AS)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Modus studi daya guna"</string>
 </resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index d122f50..f67c3e4 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -24,6 +24,12 @@
     <string name="english_ime_settings" msgid="6661589557206947774">"Impostazioni tastiera Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opzioni inserimento"</string>
     <string name="spell_checker_service_name" msgid="2003013122022285508">"Correzione Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrazione tasti"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Suono tasti"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Popup sui tasti"</string>
@@ -63,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Invia"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Altro"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Attesa"</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index b490913..31632e9 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"מקלדת Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"הגדרות מקלדת של Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"אפשרויות קלט"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"תיקון Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"רטט עם לחיצה על מקשים"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"צלילים עם לחיצה על מקשים"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"שלח"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"אבג"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"עוד"</string>
     <string name="label_pause_key" msgid="181098308428035340">"השהה"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"המתן"</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 8ac407a..08b5722 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Androidキーボード"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Androidキーボードの設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"入力オプション"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android校正"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"キー操作バイブ"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"キー操作音"</string>
@@ -133,7 +138,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"記号キーボードのマイク"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"音声入力は無効です"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"入力方法の選択"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"入力方法の設定"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"入力方法を設定"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"入力言語"</string>
     <string name="select_language" msgid="2573265881207142437">"入力言語の選択"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"←保存するにはもう一度タップ"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index c9000d1..83cf906 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android 키보드"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 키보드 설정"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"입력 옵션"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 수정"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"키를 누를 때 진동 발생"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"키를 누를 때 소리 발생"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"전송"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"더보기"</string>
     <string name="label_pause_key" msgid="181098308428035340">"일시 중지"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"대기"</string>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index 4113ef3..24073f7 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"„Android“ klaviatūra"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"„Android“ klaviatūros nustatymai"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Įvesties parinktys"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"„Android“ korekcijos"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibruoti, kai paspaudžiami klavišai"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Klavišo paspaudimo garsas"</string>
@@ -143,8 +148,8 @@
     <string name="prefs_enable_recorrection" msgid="4588408906649533582">"Jei norite ištais. žodž., paliesk."</string>
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Jei norite ištaisyti įvestus žodžius, palieskite juos tik tada, kai matomi pasiūlymai"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Klaviatūros tema"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY klaviatūra vokiečių k."</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"Anglų k. (JK)"</string>
+    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Vokiška QWERTY klaviatūra"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Anglų (JK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"Anglų k. (JAV)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Tinkamumo tyrimo režimas"</string>
 </resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index e5da1dc..2ec111a 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android tastatūra"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android tastatūras iestatījumi"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Ievades opcijas"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android korekcija"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrēt, nospiežot taustiņu"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Skaņa, nospiežot taustiņu"</string>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 63fd655..eef6ef2 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Papan kekunci Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Tetapan papan kekunci Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Pilihan input"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Pembetulan Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Getar pada tekanan kekunci"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Bunyi pada tekanan kekunci"</string>
@@ -134,7 +139,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Mik. pd kekunci smbl"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Input suara dilmphkn"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Pilih kaedah input"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan kaedah masukan"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Konfigurasikan kaedah input"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Bahasa input"</string>
     <string name="select_language" msgid="2573265881207142437">"Pilih bahasa input"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Sentuh sekali lagi untuk menyimpan"</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index e865822..62140d3 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Skjermtastatur"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Innstillinger for skjermtastatur"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inndataalternativer"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-stavekontroll"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrer ved tastetrykk"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Lyd ved tastetrykk"</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index ebb1c06..26e138d 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android-toetsenbord"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Instellingen voor Android-toetsenbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Invoeropties"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-spellingcontrole"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Trillen bij toetsaanslag"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Geluid bij toetsaanslag"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Verzenden"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Meer"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Onderbr."</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Wacht"</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 1cf1416..72687b5 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Klawiatura Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Ustawienia klawiatury Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opcje wprowadzania"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Korekta Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Wibracja przy naciśnięciu"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Dźwięk przy naciśnięciu"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Wyślij"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Więcej"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Czekaj"</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 22c99f2..7d1b6d0 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de introdução"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao primir as teclas"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao premir as teclas"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Mostrar popup ao premir tecla"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 167ae0e..793c78f 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Teclado Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Configurações de teclado Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opções de entrada"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Correção do Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar ao tocar a tecla"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Som ao tocar a tecla"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Enviar"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Mais"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Esp."</string>
@@ -143,7 +147,7 @@
     <string name="prefs_enable_recorrection" msgid="4588408906649533582">"Tocar para corrigir"</string>
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Toque nas palavras digitadas para corrigi-las apenas quando as sugestões estiverem visíveis"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema do teclado"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"Alemão QWERTY"</string>
+    <string name="subtype_de_qwerty" msgid="3358900499589259491">"QWERTY alemão"</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="prefs_usability_study_mode" msgid="1261130555134595254">"Modo de estudo de utilização"</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 359dc7a..9901b9f 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -26,6 +26,12 @@
     <skip />
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrar cun smatgar in buttun"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tun cun smatgar in buttun"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Pop-up cun smatgar ina tasta"</string>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index c317cfd..1dd083e 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Tastatură Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Setările tastaturii Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Opţiuni de introducere text"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Corecţie Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrare la apăsarea tastei"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Sunet la apăsarea tastei"</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 68d1ee2..64c2aa6 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Клавиатура Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Настройки клавиатуры Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Параметры ввода"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Исправления Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Виброотклик клавиш"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук клавиш"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Отправить"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"АБВ"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Ещё"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Приостановить"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Подождите"</string>
@@ -133,7 +137,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Значок на клавиатуре символов"</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голосовой ввод откл."</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Выбрать способ ввода"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"Настройка раскладки клавиатуры"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Настройка способов ввода"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Языки ввода"</string>
     <string name="select_language" msgid="2573265881207142437">"Выберите языки ввода"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Нажмите еще раз, чтобы сохранить"</string>
@@ -144,7 +148,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Нажмите на слово, чтобы исправить его (при наличии подсказок)"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Тема клавиатуры"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"Немецкая клавиатура QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"английский (Великобритания)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"английский (США)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"Английский (Великобритания)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Английский (США)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Режим проверки удобства использования"</string>
 </resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index a8c5700..3e7ed14 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Klávesnica Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavenia klávesnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti zadávania textu a údajov"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Opravy pravopisu Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Pri stlačení klávesu vibrovať"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvuk pri stlačení klávesu"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Odoslať"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Viac"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pozastaviť"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Čakajte"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 19bf04c..84498ad 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Tipkovnica Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Nastavitve tipkovnice Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Možnosti vnosa"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Popravek za Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibriranje ob pritisku tipke"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Zvok ob pritisku tipke"</string>
@@ -144,7 +149,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Dotaknite se vnesenih besed in jih popravite, samo ko so predlogi vidni"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema tipkovnice"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"Nemška QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"angl. (bri.)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"angl. (ZDA)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"angleščina (Združeno kraljestvo)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"angleščina (ZDA)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Način za preučevanje uporabnosti"</string>
 </resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 46daea9..d465aae 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android тастатура"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Подешавања Android тастатуре"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Опције уноса"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android исправљање"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вибрирај на притисак тастера"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук на притисак тастера"</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 2c8bc73..61a7f13 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Androids tangentbord"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Inställningar för Androids tangentbord"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Inmatningsalternativ"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android-korrigering"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Vibrera vid tangenttryck"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Knappljud"</string>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index a916535..9c677d1 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Chaguo za uingizaji"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tetema unabofya kitufe"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Toa sauti unapobofya kitufe"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ibuka kitufe kinapobonyezwa"</string>
@@ -81,8 +87,7 @@
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
     <skip />
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Zaidi"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pumzisha"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Subiri"</string>
diff --git a/java/res/values-sw600dp/config.xml b/java/res/values-sw600dp/config.xml
index ade376a..d539e0d 100644
--- a/java/res/values-sw600dp/config.xml
+++ b/java/res/values-sw600dp/config.xml
@@ -20,10 +20,9 @@
 
 <resources>
     <bool name="config_enable_show_settings_key_option">true</bool>
-    <bool name="config_default_show_settings_key">true</bool>
+    <bool name="config_default_show_settings_key">false</bool>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_popup_on_keypress_option">false</bool>
-    <bool name="config_enable_show_recorrection_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
     <bool name="config_sliding_key_input_enabled">false</bool>
     <bool name="config_digit_popup_characters_enabled">false</bool>
diff --git a/java/res/values-sw768dp/config.xml b/java/res/values-sw768dp/config.xml
index 091d62e..0f8f106 100644
--- a/java/res/values-sw768dp/config.xml
+++ b/java/res/values-sw768dp/config.xml
@@ -23,7 +23,6 @@
     <bool name="config_default_show_settings_key">true</bool>
     <bool name="config_enable_show_voice_key_option">false</bool>
     <bool name="config_enable_show_popup_on_keypress_option">false</bool>
-    <bool name="config_enable_show_recorrection_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">false</bool>
     <bool name="config_sliding_key_input_enabled">false</bool>
     <bool name="config_digit_popup_characters_enabled">false</bool>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 780ff02..c53ed2e 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"แป้นพิมพ์ Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"การตั้งค่าแป้นพิมพ์ Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"ตัวเลือกการป้อนข้อมูล"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"การแก้ไขของ Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"สั่นเมื่อกดปุ่ม"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"ส่งเสียงเมื่อกดปุ่ม"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"ส่ง"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"เพิ่มเติม"</string>
     <string name="label_pause_key" msgid="181098308428035340">"หยุดชั่วคราว"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"รอ"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index b0806be..447baef 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android keyboard"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Mga setting ng Android keyboard"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Mga pagpipilian sa input"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Pagwawasto sa Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Mag-vibrate sa keypress"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tunog sa keypress"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Ipadala"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Higit pa"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Intay"</string>
@@ -143,8 +147,8 @@
     <string name="prefs_enable_recorrection" msgid="4588408906649533582">"Pindutin upang itama ang mga salita"</string>
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Pindutin ang mga inilagay na salita upang iwasto ang mga ito, kapag nakikita lang ang mga suhestiyon"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Tema ng keyboard"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"German QWERTY"</string>
+    <string name="subtype_de_qwerty" msgid="3358900499589259491">"German na QWERTY"</string>
     <string name="subtype_en_GB" msgid="88170601942311355">"Ingles (UK)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"Ingles (US)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"Ingles (Estados Unidos)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Study mode ng pagiging kapaki-pakinabang"</string>
 </resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 103f016..91db66e 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android klavyesi"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android klavye ayarları"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Giriş seçenekleri"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android düzeltme"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Tuşa basıldığında titret"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Tuşa basıldığında ses çıkar"</string>
@@ -144,7 +149,7 @@
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"Yalnızca öneriler görünür olduğunda, düzeltmek için girilen kelimelere dokunun"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"Klavye teması"</string>
     <string name="subtype_de_qwerty" msgid="3358900499589259491">"Almanca QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"İngilizce (İngiltere)"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"İngilizce (BK)"</string>
     <string name="subtype_en_US" msgid="6160452336634534239">"İngilizce (ABD)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"Kullanılabilirlik çalışması modu"</string>
 </resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index cca56b8..3e55f46 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Клавіатура Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Налашт-ня клавіат. Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Парам. введення"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Виправлення Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Вібр при натиску клав."</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Звук при натиску клав."</string>
@@ -133,7 +138,7 @@
     <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"Miкр. на симв. клавіат."</string>
     <string name="voice_input_modes_summary_off" msgid="63875609591897607">"Голос. ввід вимкнено"</string>
     <string name="selectInputMethod" msgid="315076553378705821">"Вибрати метод введення"</string>
-    <string name="configure_input_method" msgid="373356270290742459">"Налаштувати методи введення"</string>
+    <string name="configure_input_method" msgid="373356270290742459">"Налаштування методів введення"</string>
     <string name="language_selection_title" msgid="1651299598555326750">"Мови вводу"</string>
     <string name="select_language" msgid="2573265881207142437">"Вибрати мову введення"</string>
     <string name="hint_add_to_dictionary" msgid="9006292060636342317">"← Торкн. ще, щоб збер."</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index e7c684d..caf9d57 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Bàn phím Android"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Cài đặt bàn phím Android"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"Tùy chọn nhập"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Dịch vụ sửa chính tả của Android"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Rung khi nhấn phím"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Âm thanh khi nhấn phím"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"Gửi"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Khác"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Tạm dừng"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Đợi"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 189c174..cdbd64d 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android 键盘"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 键盘设置"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"输入选项"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 更正"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按键时振动"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按键时播放音效"</string>
@@ -64,8 +69,7 @@
     <string name="label_send_key" msgid="2815056534433717444">"发送"</string>
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <string name="label_to_symbol_key" msgid="8516904117128967293">"?123"</string>
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"更多"</string>
     <string name="label_pause_key" msgid="181098308428035340">"暂停"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"等待"</string>
@@ -143,8 +147,8 @@
     <string name="prefs_enable_recorrection" msgid="4588408906649533582">"触摸以更正字词"</string>
     <string name="prefs_enable_recorrection_summary" msgid="5082041365862396329">"仅在系统显示建议后,才触摸输入的字词进行更正"</string>
     <string name="keyboard_layout" msgid="8451164783510487501">"键盘主题"</string>
-    <string name="subtype_de_qwerty" msgid="3358900499589259491">"德语 QWERTY"</string>
-    <string name="subtype_en_GB" msgid="88170601942311355">"英语(英式)"</string>
-    <string name="subtype_en_US" msgid="6160452336634534239">"英语(美式)"</string>
+    <string name="subtype_de_qwerty" msgid="3358900499589259491">"德语 QWERTY 键盘"</string>
+    <string name="subtype_en_GB" msgid="88170601942311355">"英语(英国)"</string>
+    <string name="subtype_en_US" msgid="6160452336634534239">"英语(美国)"</string>
     <string name="prefs_usability_study_mode" msgid="1261130555134595254">"可用性研究模式"</string>
 </resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index aa65cdd..fcf5118 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -23,7 +23,12 @@
     <string name="english_ime_name" msgid="7252517407088836577">"Android 鍵盤"</string>
     <string name="english_ime_settings" msgid="6661589557206947774">"Android 鍵盤設定"</string>
     <string name="english_ime_input_options" msgid="3909945612939668554">"輸入選項"</string>
-    <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
+    <string name="spell_checker_service_name" msgid="2003013122022285508">"Android 修正"</string>
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
     <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"按鍵時震動"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"按鍵時播放音效"</string>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 4929c12..b4688fd 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -25,6 +25,12 @@
     <string name="english_ime_input_options" msgid="3909945612939668554">"Okukhethwa kukho kokungenayo"</string>
     <!-- no translation found for spell_checker_service_name (2003013122022285508) -->
     <skip />
+    <!-- no translation found for android_spell_checker_settings (5822324635435443689) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_title (7469233942295924620) -->
+    <skip />
+    <!-- no translation found for use_proximity_option_summary (2857708859847261945) -->
+    <skip />
     <string name="vibrate_on_keypress" msgid="5258079494276955460">"Dlidlizelisa ngokucindezela inkinobho"</string>
     <string name="sound_on_keypress" msgid="6093592297198243644">"Umsindo wokucindezela ukhiye"</string>
     <string name="popup_on_keypress" msgid="123894815723512944">"Ugaxekile ngokucindezela ukhiye"</string>
@@ -81,8 +87,7 @@
     <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string>
     <!-- no translation found for label_to_symbol_key (8516904117128967293) -->
     <skip />
-    <!-- no translation found for label_to_symbol_with_microphone_key (9035925553010061906) -->
-    <skip />
+    <string name="label_to_symbol_with_microphone_key" msgid="9035925553010061906">"123"</string>
     <string name="label_more_key" msgid="3760239494604948502">"Okungaphezulu"</string>
     <string name="label_pause_key" msgid="181098308428035340">"Misa okwesikhashana"</string>
     <string name="label_wait_key" msgid="6402152600878093134">"Linda"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 172ca2f..c2200b5 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -107,6 +107,7 @@
     </declare-styleable>
 
     <declare-styleable name="CandidateView">
+        <attr name="autoCorrectionVisualFlashEnabled" format="boolean" />
         <attr name="autoCorrectHighlight" format="integer">
             <flag name="autoCorrectBold" value="0x01" />
             <flag name="autoCorrectUnderline" value="0x02" />
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index 731f63f..6327ede 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -23,7 +23,7 @@
     <bool name="config_default_show_settings_key">false</bool>
     <bool name="config_enable_show_voice_key_option">true</bool>
     <bool name="config_enable_show_popup_on_keypress_option">true</bool>
-    <bool name="config_enable_show_recorrection_option">true</bool>
+    <bool name="config_enable_show_recorrection_option">false</bool>
     <bool name="config_enable_bigram_suggestions_option">true</bool>
     <bool name="config_enable_usability_study_mode_option">false</bool>
     <bool name="config_sliding_key_input_enabled">true</bool>
@@ -36,9 +36,10 @@
     <!-- Default value for bigram prediction: after entering a word and a space only, should we look
          at input history to suggest a hopefully helpful candidate for the next word? -->
     <bool name="config_default_bigram_prediction">false</bool>
-    <bool name="config_default_recorrection_enabled">true</bool>
+    <bool name="config_default_compat_recorrection_enabled">true</bool>
     <bool name="config_default_sound_enabled">false</bool>
     <bool name="config_auto_correction_spacebar_led_enabled">true</bool>
+    <bool name="config_auto_correction_suggestion_strip_visual_flash_enabled">false</bool>
     <!-- Showing mini keyboard, just above the touched point if true, aligned to the key if false -->
     <bool name="config_show_mini_keyboard_at_touched_point">false</bool>
     <!-- The language is never displayed if == 0, always displayed if < 0 -->
diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml
index fb28766..f55e9bf 100644
--- a/java/res/values/donottranslate.xml
+++ b/java/res/values/donottranslate.xml
@@ -153,4 +153,8 @@
 
     <!-- Generic subtype label -->
     <string name="subtype_generic">%s</string>
+
+    <!-- dictionary pack package name /settings activity (for shared prefs and settings) -->
+    <string name="dictionary_pack_package_name">com.google.android.inputmethod.latin.dictionarypack</string>
+    <string name="dictionary_pack_settings_activity">com.google.android.inputmethod.latin.dictionarypack.DictionarySettingsActivity</string>
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index ed69fc9..247bdba 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -28,6 +28,15 @@
     <!-- Title for Latin Keyboard spell checker service -->
     <string name="spell_checker_service_name">Android correction</string>
 
+    <!-- Title for the spell checking service settings screen -->
+    <string name="android_spell_checker_settings">Spell checking settings</string>
+
+    <!-- Title for the "use proximity" option for spell checking [CHAR LIMIT=25] -->
+    <string name="use_proximity_option_title">Use proximity data</string>
+
+    <!-- Description for the "use proximity" option for spell checking [CHAR LIMIT=65] -->
+    <string name="use_proximity_option_summary">Use a keyboard-like proximity algorithm for spell checking</string>
+
     <!-- Option to provide vibrate/haptic feedback on keypress -->
     <string name="vibrate_on_keypress">Vibrate on keypress</string>
 
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 1ebd2ce..a47eeed 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -85,6 +85,7 @@
         <item name="android:background">@drawable/candidate_feedback_background</item>
     </style>
     <style name="CandidateViewStyle" parent="SuggestionsStripBackgroundStyle">
+        <item name="autoCorrectionVisualFlashEnabled">@bool/config_auto_correction_suggestion_strip_visual_flash_enabled</item>
         <item name="autoCorrectHighlight">autoCorrectBold</item>
         <item name="colorTypedWord">#FFFFFFFF</item>
         <item name="colorAutoCorrect">#FFFCAE00</item>
@@ -188,6 +189,7 @@
         <item name="android:background">@drawable/keyboard_popup_panel_background_holo</item>
     </style>
     <style name="CandidateViewStyle.IceCreamSandwich" parent="SuggestionsStripBackgroundStyle.IceCreamSandwich">
+        <item name="autoCorrectionVisualFlashEnabled">@bool/config_auto_correction_suggestion_strip_visual_flash_enabled</item>
         <item name="autoCorrectHighlight">autoCorrectBold|autoCorrectInvert</item>
         <item name="colorTypedWord">#FFFFFFFF</item>
         <item name="colorAutoCorrect">#FF3DC8FF</item>
diff --git a/java/res/xml-sw600dp/kbd_rows_russian.xml b/java/res/xml-sw600dp/kbd_rows_russian.xml
index 7588f6c..2f4b95e 100644
--- a/java/res/xml-sw600dp/kbd_rows_russian.xml
+++ b/java/res/xml-sw600dp/kbd_rows_russian.xml
@@ -105,8 +105,7 @@
         <Key
             latin:keyLabel="т" />
         <Key
-            latin:keyLabel="ь"
-            latin:popupCharacters="@string/alternates_for_cyrillic_soft_sign" />
+            latin:keyLabel="ь" />
         <Key
             latin:keyLabel="б" />
         <Key
diff --git a/java/res/xml/kbd_rows_russian.xml b/java/res/xml/kbd_rows_russian.xml
index 216d749..3aeb52b 100644
--- a/java/res/xml/kbd_rows_russian.xml
+++ b/java/res/xml/kbd_rows_russian.xml
@@ -69,6 +69,7 @@
             latin:popupCharacters="0" />
         <Key
             latin:keyLabel="х"
+            latin:popupCharacters="@string/alternates_for_cyrillic_soft_sign"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 552e3cf..6d2218d 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -48,12 +48,12 @@
             android:title="@string/prefs_enable_recorrection"
             android:summary="@string/prefs_enable_recorrection_summary"
             android:persistent="true"
-            android:defaultValue="@bool/config_default_recorrection_enabled" />
+            android:defaultValue="@bool/config_default_compat_recorrection_enabled" />
         <CheckBoxPreference
             android:key="show_settings_key"
             android:title="@string/prefs_settings_key"
             android:persistent="true"
-            android:defaultValue="false" />
+            android:defaultValue="@bool/config_default_show_settings_key" />
         <ListPreference
             android:key="voice_mode"
             android:title="@string/voice_input"
@@ -70,8 +70,8 @@
             android:title="@string/configure_dictionaries_title">
            <intent
               android:action="android.intent.action.MAIN"
-              android:targetPackage="com.google.android.inputmethod.latin.dictionarypack"
-              android:targetClass="com.google.android.inputmethod.latin.dictionarypack.DictionarySettingsActivity" />
+              android:targetPackage="@string/dictionary_pack_package_name"
+              android:targetClass="@string/dictionary_pack_settings_activity" />
         </PreferenceScreen>
         <ListPreference
             android:key="auto_correction_threshold"
diff --git a/java/res/xml/spell_checker_settings.xml b/java/res/xml/spell_checker_settings.xml
new file mode 100644
index 0000000..f402555
--- /dev/null
+++ b/java/res/xml/spell_checker_settings.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:title="@string/android_spell_checker_settings">
+  <CheckBoxPreference
+     android:key="use_proximity"
+     android:title="@string/use_proximity_option_title"
+     android:summary="@string/use_proximity_option_summary"
+     android:persistent="true"
+     android:defaultValue="true" />
+</PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
index d40728d..bf2512d 100644
--- a/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
+++ b/java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java
@@ -271,9 +271,10 @@
         // but always use the default setting defined in the resources.
         if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) {
             mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
-                    res.getBoolean(R.bool.config_default_recorrection_enabled));
+                    res.getBoolean(R.bool.config_default_compat_recorrection_enabled));
         } else {
-            mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
+            mRecorrectionEnabled =
+                    res.getBoolean(R.bool.config_default_compat_recorrection_enabled);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 811470c..21477a9 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -280,7 +280,6 @@
             mSymbolsKeyboardId = getKeyboardId(editorInfo, true, false, settingsValues);
             mSymbolsShiftedKeyboardId = getKeyboardId(editorInfo, true, true, settingsValues);
             setKeyboard(getKeyboard(mSavedKeyboardState.getKeyboardId()));
-            updateShiftState();
         } catch (RuntimeException e) {
             Log.w(TAG, "loading keyboard failed: " + mMainKeyboardId, e);
             LatinImeLogger.logOnException(mMainKeyboardId.toString(), e);
@@ -331,6 +330,7 @@
         final boolean localeChanged = (oldKeyboard == null)
                 || !keyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
         mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
+        updateShiftState();
     }
 
     private int getSwitchState(KeyboardId id) {
@@ -543,11 +543,12 @@
     }
 
     private void setAutomaticTemporaryUpperCase() {
-        LatinKeyboard latinKeyboard = getLatinKeyboard();
-        if (latinKeyboard != null) {
-            latinKeyboard.setAutomaticTemporaryUpperCase();
-            mKeyboardView.invalidateAllKeys();
+        if (mKeyboardView == null) return;
+        final Keyboard keyboard = mKeyboardView.getKeyboard();
+        if (keyboard != null) {
+            keyboard.setAutomaticTemporaryUpperCase();
         }
+        mKeyboardView.invalidateAllKeys();
     }
 
     /**
@@ -559,7 +560,9 @@
             Log.d(TAG, "updateShiftState:"
                     + " autoCaps=" + mInputMethodService.getCurrentAutoCapsState()
                     + " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
-                    + " shiftKeyState=" + shiftKeyState);
+                    + " shiftKeyState=" + shiftKeyState
+                    + " isAlphabetMode=" + isAlphabetMode()
+                    + " isShiftLocked=" + isShiftLocked());
         if (isAlphabetMode()) {
             if (!isShiftLocked() && !shiftKeyState.isIgnoring()) {
                 if (shiftKeyState.isReleasing() && mInputMethodService.getCurrentAutoCapsState()) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
index 0cde4e5..fd98456 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java
@@ -21,7 +21,7 @@
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 
 public class KeyboardShiftState {
-    private static final String TAG = "KeyboardShiftState";
+    private static final String TAG = KeyboardShiftState.class.getSimpleName();
     private static final boolean DEBUG = KeyboardSwitcher.DEBUG_STATE;
 
     private static final int NORMAL = 0;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 2d50a6f..3da670e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -25,7 +25,6 @@
 import android.text.TextUtils;
 import android.util.Log;
 
-import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -55,66 +54,6 @@
     }
 
     /**
-     * Escapes a string for any characters that may be suspicious for a file or directory name.
-     *
-     * Concretely this does a sort of URL-encoding except it will encode everything that's not
-     * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
-     * we cannot allow here)
-     */
-    // TODO: create a unit test for this method
-    private static String replaceFileNameDangerousCharacters(String name) {
-        // This assumes '%' is fully available as a non-separator, normal
-        // character in a file name. This is probably true for all file systems.
-        final StringBuilder sb = new StringBuilder();
-        for (int i = 0; i < name.length(); ++i) {
-            final int codePoint = name.codePointAt(i);
-            if (Character.isLetterOrDigit(codePoint) || '_' == codePoint) {
-                sb.appendCodePoint(codePoint);
-            } else {
-                sb.append('%');
-                sb.append(Integer.toHexString(codePoint));
-            }
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Find out the cache directory associated with a specific locale.
-     */
-    private static String getCacheDirectoryForLocale(Locale locale, Context context) {
-        final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString());
-        final String absoluteDirectoryName = context.getFilesDir() + File.separator
-                + relativeDirectoryName;
-        final File directory = new File(absoluteDirectoryName);
-        if (!directory.exists()) {
-            if (!directory.mkdirs()) {
-                Log.e(TAG, "Could not create the directory for locale" + locale);
-            }
-        }
-        return absoluteDirectoryName;
-    }
-
-    /**
-     * Generates a file name for the id and locale passed as an argument.
-     *
-     * In the current implementation the file name returned will always be unique for
-     * any id/locale pair, but please do not expect that the id can be the same for
-     * different dictionaries with different locales. An id should be unique for any
-     * dictionary.
-     * The file name is pretty much an URL-encoded version of the id inside a directory
-     * named like the locale, except it will also escape characters that look dangerous
-     * to some file systems.
-     * @param id the id of the dictionary for which to get a file name
-     * @param locale the locale for which to get the file name
-     * @param context the context to use for getting the directory
-     * @return the name of the file to be created
-     */
-    private static String getCacheFileName(String id, Locale locale, Context context) {
-        final String fileName = replaceFileNameDangerousCharacters(id);
-        return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
-    }
-
-    /**
      * Return for a given locale or dictionary id the provider URI to get the dictionary.
      */
     private static Uri getProviderUri(String path) {
@@ -149,32 +88,32 @@
     }
 
     /**
-     * Queries a content provider for dictionary data for some locale and returns the file addresses
+     * Queries a content provider for dictionary data for some locale and cache the returned files
      *
-     * This will query a content provider for dictionary data for a given locale, and return
-     * the addresses of a file set the members of which are suitable to be mmap'ed. It will copy
-     * them to local storage if needed.
-     * It should also check the dictionary versions to avoid unnecessary copies but this is
-     * still in TODO state.
-     * This will make the data from the content provider the cached dictionary for this locale,
-     * overwriting any previous cached data.
+     * This will query a content provider for dictionary data for a given locale, and copy the
+     * files locally so that they can be mmap'ed. This may overwrite previously cached dictionaries
+     * with newer versions if a newer version is made available by the content provider.
      * @returns the addresses of the files, or null if no data could be obtained.
      * @throw FileNotFoundException if the provider returns non-existent data.
      * @throw IOException if the provider-returned data could not be read.
      */
-    public static List<AssetFileAddress> getDictSetFromContentProvider(final Locale locale,
+    public static List<AssetFileAddress> cacheDictionariesFromContentProvider(final Locale locale,
             final Context context) throws FileNotFoundException, IOException {
         final ContentResolver resolver = context.getContentResolver();
         final List<String> idList = getDictIdList(locale, context);
         final List<AssetFileAddress> fileAddressList = new ArrayList<AssetFileAddress>();
         for (String id : idList) {
-            final Uri dictionaryPackUri = getProviderUri(id);
+            final Uri wordListUri = getProviderUri(id);
             final AssetFileDescriptor afd =
-                    resolver.openAssetFileDescriptor(dictionaryPackUri, "r");
+                    resolver.openAssetFileDescriptor(wordListUri, "r");
             if (null == afd) continue;
             final String fileName = copyFileTo(afd.createInputStream(),
-                    getCacheFileName(id, locale, context));
+                    BinaryDictionaryGetter.getCacheFileName(id, locale, context));
             afd.close();
+            if (0 >= resolver.delete(wordListUri, null, null)) {
+                // I'd rather not print the word list ID to the log here out of security concerns
+                Log.e(TAG, "Could not have the dictionary pack delete a word list");
+            }
             fileAddressList.add(AssetFileAddress.makeFromFileName(fileName));
         }
         return fileAddressList;
@@ -192,7 +131,9 @@
         final Locale savedLocale = Utils.setSystemLocale(res, locale);
         final InputStream stream = res.openRawResource(resource);
         Utils.setSystemLocale(res, savedLocale);
-        return copyFileTo(stream, getCacheFileName(Integer.toString(resource), locale, context));
+        return copyFileTo(stream,
+                BinaryDictionaryGetter.getCacheFileName(Integer.toString(resource),
+                        locale, context));
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 4b1c05a..360cf21 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -17,13 +17,17 @@
 package com.android.inputmethod.latin;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.util.Log;
 
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
@@ -37,10 +41,105 @@
      */
     private static final String TAG = BinaryDictionaryGetter.class.getSimpleName();
 
+    /**
+     * Name of the common preferences name to know which word list are on and which are off.
+     */
+    private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs";
+
     // Prevents this from being instantiated
     private BinaryDictionaryGetter() {}
 
     /**
+     * Returns whether we may want to use this character as part of a file name.
+     *
+     * This basically only accepts ascii letters and numbers, and rejects everything else.
+     */
+    private static boolean isFileNameCharacter(int codePoint) {
+        if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
+        if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
+        if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
+        return codePoint == '_'; // Underscore
+    }
+
+    /**
+     * Escapes a string for any characters that may be suspicious for a file or directory name.
+     *
+     * Concretely this does a sort of URL-encoding except it will encode everything that's not
+     * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
+     * we cannot allow here)
+     */
+    // TODO: create a unit test for this method
+    private static String replaceFileNameDangerousCharacters(final String name) {
+        // This assumes '%' is fully available as a non-separator, normal
+        // character in a file name. This is probably true for all file systems.
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < name.length(); ++i) {
+            final int codePoint = name.codePointAt(i);
+            if (isFileNameCharacter(codePoint)) {
+                sb.appendCodePoint(codePoint);
+            } else {
+                // 6 digits - unicode is limited to 21 bits
+                sb.append(String.format((Locale)null, "%%%1$06x", codePoint));
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Reverse escaping done by replaceFileNameDangerousCharacters.
+     */
+    private static String getWordListIdFromFileName(final String fname) {
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < fname.length(); ++i) {
+            final int codePoint = fname.codePointAt(i);
+            if ('%' != codePoint) {
+                sb.appendCodePoint(codePoint);
+            } else {
+                final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16);
+                i += 6;
+                sb.appendCodePoint(encodedCodePoint);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Find out the cache directory associated with a specific locale.
+     */
+    private static String getCacheDirectoryForLocale(Locale locale, Context context) {
+        final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale.toString());
+        final String absoluteDirectoryName = context.getFilesDir() + File.separator
+                + relativeDirectoryName;
+        final File directory = new File(absoluteDirectoryName);
+        if (!directory.exists()) {
+            if (!directory.mkdirs()) {
+                Log.e(TAG, "Could not create the directory for locale" + locale);
+            }
+        }
+        return absoluteDirectoryName;
+    }
+
+    /**
+     * Generates a file name for the id and locale passed as an argument.
+     *
+     * In the current implementation the file name returned will always be unique for
+     * any id/locale pair, but please do not expect that the id can be the same for
+     * different dictionaries with different locales. An id should be unique for any
+     * dictionary.
+     * The file name is pretty much an URL-encoded version of the id inside a directory
+     * named like the locale, except it will also escape characters that look dangerous
+     * to some file systems.
+     * @param id the id of the dictionary for which to get a file name
+     * @param locale the locale for which to get the file name
+     * @param context the context to use for getting the directory
+     * @return the name of the file to be created
+     */
+    public static String getCacheFileName(String id, Locale locale, Context context) {
+        final String fileName = replaceFileNameDangerousCharacters(id);
+        return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
+    }
+
+    /**
      * Returns a file address from a resource, or null if it cannot be opened.
      */
     private static AssetFileAddress loadFallbackResource(final Context context,
@@ -60,6 +159,48 @@
     }
 
     /**
+     * Returns the list of cached files for a specific locale.
+     *
+     * @param locale the locale to find the dictionary files for.
+     * @param context the context on which to open the files upon.
+     * @return a list of binary dictionary files, which may be null but may not be empty.
+     */
+    private static List<AssetFileAddress> getCachedDictionaryList(final Locale locale,
+            final Context context) {
+        final String directoryName = getCacheDirectoryForLocale(locale, context);
+        final File[] cacheFiles = new File(directoryName).listFiles();
+        // TODO: Never return null. Fallback on the built-in dictionary, and if that's
+        // not present or disabled, then return an empty list.
+        if (null == cacheFiles) return null;
+
+        final SharedPreferences dictPackSettings;
+        try {
+            final String dictPackName = context.getString(R.string.dictionary_pack_package_name);
+            final Context dictPackContext = context.createPackageContext(dictPackName, 0);
+            dictPackSettings = dictPackContext.getSharedPreferences(COMMON_PREFERENCES_NAME,
+                    Context.MODE_WORLD_READABLE | Context.MODE_MULTI_PROCESS);
+        } catch (NameNotFoundException e) {
+            // The dictionary pack is not installed...
+            // TODO: fallback on the built-in dict, see the TODO above
+            Log.e(TAG, "Could not find a dictionary pack");
+            return null;
+        }
+
+        final ArrayList<AssetFileAddress> fileList = new ArrayList<AssetFileAddress>();
+        for (File f : cacheFiles) {
+            final String wordListId = getWordListIdFromFileName(f.getName());
+            final boolean isActive = dictPackSettings.getBoolean(wordListId, true);
+            if (!isActive) continue;
+            if (f.canRead()) {
+                fileList.add(AssetFileAddress.makeFromFileName(f.getPath()));
+            } else {
+                Log.e(TAG, "Found a cached dictionary file but cannot read it");
+            }
+        }
+        return fileList.size() > 0 ? fileList : null;
+    }
+
+    /**
      * Returns a list of file addresses for a given locale, trying relevant methods in order.
      *
      * Tries to get binary dictionaries from various sources, in order:
@@ -71,13 +212,16 @@
      * - Returns null.
      * @return The address of a valid file, or null.
      */
-    public static List<AssetFileAddress> getDictionaryFiles(Locale locale, Context context,
-            int fallbackResId) {
+    public static List<AssetFileAddress> getDictionaryFiles(final Locale locale,
+            final Context context, final int fallbackResId) {
         try {
-            List<AssetFileAddress> listFromContentProvider =
-                    BinaryDictionaryFileDumper.getDictSetFromContentProvider(locale, context);
-            if (null != listFromContentProvider) {
-                return listFromContentProvider;
+            // cacheDictionariesFromContentProvider returns the list of files it copied to local
+            // storage, but we don't really care about what was copied NOW: what we want is the
+            // list of everything we ever cached, so we ignore the return value.
+            BinaryDictionaryFileDumper.cacheDictionariesFromContentProvider(locale, context);
+            List<AssetFileAddress> cachedDictionaryList = getCachedDictionaryList(locale, context);
+            if (null != cachedDictionaryList) {
+                return cachedDictionaryList;
             }
             // If the list is null, fall through and return the fallback
         } catch (FileNotFoundException e) {
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index d779c85..0640fd0 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -278,6 +278,7 @@
 
         private final ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>();
 
+        public final boolean mAutoCorrectionVisualFlashEnabled;
         public boolean mMoreSuggestionsAvailable;
 
         public SuggestionsStripParams(Context context, AttributeSet attrs, int defStyle,
@@ -285,6 +286,8 @@
             super(words, dividers, infos);
             final TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.CandidateView, defStyle, R.style.CandidateViewStyle);
+            mAutoCorrectionVisualFlashEnabled = a.getBoolean(
+                    R.styleable.CandidateView_autoCorrectionVisualFlashEnabled, false);
             mAutoCorrectHighlight = a.getInt(R.styleable.CandidateView_autoCorrectHighlight, 0);
             mColorTypedWord = a.getColor(R.styleable.CandidateView_colorTypedWord, 0);
             mColorAutoCorrect = a.getColor(R.styleable.CandidateView_colorAutoCorrect, 0);
@@ -700,6 +703,9 @@
     }
 
     public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) {
+        if (!mStripParams.mAutoCorrectionVisualFlashEnabled) {
+            return;
+        }
         final CharSequence inverted = mStripParams.getInvertedText(autoCorrectedWord);
         if (inverted == null)
             return;
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index c28e40d..a932f03 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -190,8 +190,7 @@
     private long mLastKeyTime;
 
     private AudioManager mAudioManager;
-    // Align sound effect volume on music volume
-    private static final float FX_VOLUME = -1.0f;
+    private static float mFxVolume = -1.0f; // just a default value to be updated runtime
     private boolean mSilentModeOn; // System-wide current configuration
 
     // TODO: Move this flag to VoiceProxy
@@ -510,6 +509,7 @@
         if (null == mSubtypeSwitcher) mSubtypeSwitcher = SubtypeSwitcher.getInstance();
         mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr());
         resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
+        updateSoundEffectVolume();
     }
 
     private void initSuggest() {
@@ -2068,14 +2068,24 @@
         }
     };
 
+    // update sound effect volume
+    private void updateSoundEffectVolume() {
+        if (mAudioManager == null) {
+            mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            if (mAudioManager == null) return;
+        }
+        // This aligns with the current media volume minus 6dB
+        mFxVolume = (float) mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
+                / (float) mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) / 4.0f;
+    }
+
     // update flags for silent mode
     private void updateRingerMode() {
         if (mAudioManager == null) {
             mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            if (mAudioManager == null) return;
         }
-        if (mAudioManager != null) {
-            mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
-        }
+        mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
     }
 
     private void playKeyClick(int primaryCode) {
@@ -2087,8 +2097,6 @@
             }
         }
         if (isSoundOn()) {
-            // FIXME: Volume and enable should come from UI settings
-            // FIXME: These should be triggered after auto-repeat logic
             int sound = AudioManager.FX_KEYPRESS_STANDARD;
             switch (primaryCode) {
                 case Keyboard.CODE_DELETE:
@@ -2101,7 +2109,7 @@
                     sound = AudioManager.FX_KEYPRESS_SPACEBAR;
                     break;
             }
-            mAudioManager.playSoundEffect(sound, FX_VOLUME);
+            mAudioManager.playSoundEffect(sound, mFxVolume);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f10b1b8..0a391a7 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -318,7 +318,7 @@
                 // when the API level is 10 or previous.
                 mService.notifyOnCurrentInputMethodSubtypeChanged(subtype);
             }
-        }.execute();
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     public Drawable getShortcutIcon() {
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
new file mode 100644
index 0000000..483679a
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java
@@ -0,0 +1,43 @@
+/**
+ * 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.spellcheck;
+
+import com.android.inputmethod.latin.R;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+import java.util.List;
+
+/**
+ * Spell checker preference screen.
+ */
+public class SpellCheckerSettingsActivity extends PreferenceActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public Intent getIntent() {
+        final Intent modIntent = new Intent(super.getIntent());
+        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, SpellCheckerSettingsFragment.class.getName());
+        modIntent.putExtra(EXTRA_NO_HEADERS, true);
+        return modIntent;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
new file mode 100644
index 0000000..9b821be
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -0,0 +1,41 @@
+/**
+ * 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.spellcheck;
+
+import com.android.inputmethod.latin.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+/**
+ * Preference screen.
+ */
+public class SpellCheckerSettingsFragment extends PreferenceFragment {
+    private static final String TAG = SpellCheckerSettingsFragment.class.getSimpleName();
+
+    /**
+     * Empty constructor for fragment generation.
+     */
+    public SpellCheckerSettingsFragment() {
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        addPreferencesFromResource(R.xml.spell_checker_settings);
+    }
+}
diff --git a/native/src/correction.cpp b/native/src/correction.cpp
index 6d682c0..a4090a9 100644
--- a/native/src/correction.cpp
+++ b/native/src/correction.cpp
@@ -21,6 +21,7 @@
 #define LOG_TAG "LatinIME: correction.cpp"
 
 #include "correction.h"
+#include "dictionary.h"
 #include "proximity_info.h"
 
 namespace latinime {
@@ -49,12 +50,21 @@
     mInputLength = inputLength;
     mMaxDepth = maxDepth;
     mMaxEditDistance = mInputLength < 5 ? 2 : mInputLength / 2;
-    mSkippedOutputIndex = -1;
+}
+
+void Correction::initCorrectionState(
+        const int rootPos, const int childCount, const bool traverseAll) {
+    latinime::initCorrectionState(mCorrectionStates, rootPos, childCount, traverseAll);
+    // TODO: remove
+    mCorrectionStates[0].mSkipPos = mSkipPos;
 }
 
 void Correction::setCorrectionParams(const int skipPos, const int excessivePos,
         const int transposedPos, const int spaceProximityPos, const int missingSpacePos) {
+    // TODO: remove
     mSkipPos = skipPos;
+    // TODO: remove
+    mCorrectionStates[0].mSkipPos = skipPos;
     mExcessivePos = excessivePos;
     mTransposedPos = transposedPos;
     mSpaceProximityPos = spaceProximityPos;
@@ -83,33 +93,37 @@
     if (mProximityInfo->sameAsTyped(mWord, outputIndex + 1) || outputIndex < MIN_SUGGEST_DEPTH) {
         return -1;
     }
+
     *word = mWord;
     const bool sameLength = (mExcessivePos == mInputLength - 1) ? (mInputLength == inputIndex + 2)
             : (mInputLength == inputIndex + 1);
     return Correction::RankingAlgorithm::calculateFinalFreq(
-            inputIndex, outputIndex, mMatchedCharCount, freq, sameLength, this);
+            inputIndex, outputIndex, freq, sameLength, mEditDistanceTable, this);
 }
 
-void Correction::initProcessState(const int matchCount, const int inputIndex,
-        const int outputIndex, const bool traverseAllNodes, const int diffs) {
-    mMatchedCharCount = matchCount;
-    mInputIndex = inputIndex;
+bool Correction::initProcessState(const int outputIndex) {
+    if (mCorrectionStates[outputIndex].mChildCount <= 0) {
+        return false;
+    }
     mOutputIndex = outputIndex;
-    mTraverseAllNodes = traverseAllNodes;
-    mDiffs = diffs;
+    --(mCorrectionStates[outputIndex].mChildCount);
+    mInputIndex = mCorrectionStates[outputIndex].mInputIndex;
+    mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes;
+    mProximityCount = mCorrectionStates[outputIndex].mProximityCount;
+    mSkippedCount = mCorrectionStates[outputIndex].mSkippedCount;
+    mSkipPos = mCorrectionStates[outputIndex].mSkipPos;
+    mSkipping = false;
+    mProximityMatching = false;
+    mMatching = false;
+    return true;
 }
 
-void Correction::getProcessState(int *matchedCount, int *inputIndex, int *outputIndex,
-        bool *traverseAllNodes, int *diffs) {
-    *matchedCount = mMatchedCharCount;
-    *inputIndex = mInputIndex;
-    *outputIndex = mOutputIndex;
-    *traverseAllNodes = mTraverseAllNodes;
-    *diffs = mDiffs;
-}
-
-void Correction::charMatched() {
-    ++mMatchedCharCount;
+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
@@ -123,8 +137,8 @@
 }
 
 // TODO: remove
-bool Correction::needsToTraverseAll() {
-    return mTraverseAllNodes;
+bool Correction::needsToTraverseAllNodes() {
+    return mNeedsToTraverseAllNodes;
 }
 
 void Correction::incrementInputIndex() {
@@ -133,21 +147,32 @@
 
 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].mProximityCount = mProximityCount;
+    mCorrectionStates[mOutputIndex].mSkippedCount = mSkippedCount;
+    mCorrectionStates[mOutputIndex].mSkipping = mSkipping;
+    mCorrectionStates[mOutputIndex].mSkipPos = mSkipPos;
+    mCorrectionStates[mOutputIndex].mMatching = mMatching;
+    mCorrectionStates[mOutputIndex].mProximityMatching = mProximityMatching;
 }
 
-void Correction::startTraverseAll() {
-    mTraverseAllNodes = true;
+void Correction::startToTraverseAllNodes() {
+    mNeedsToTraverseAllNodes = true;
 }
 
 bool Correction::needsToPrune() const {
     return (mOutputIndex - 1 >= (mTransposedPos >= 0 ? mInputLength - 1 : mMaxDepth)
-            || mDiffs > mMaxEditDistance);
+            || mProximityCount > mMaxEditDistance);
 }
 
 Correction::CorrectionType Correction::processSkipChar(
         const int32_t c, const bool isTerminal) {
     mWord[mOutputIndex] = c;
-    if (needsToTraverseAll() && isTerminal) {
+    if (needsToTraverseAllNodes() && isTerminal) {
         mTerminalInputIndex = mInputIndex;
         mTerminalOutputIndex = mOutputIndex;
         incrementOutputIndex();
@@ -169,10 +194,31 @@
 
     bool skip = false;
     if (mSkipPos >= 0) {
+        if (mSkippedCount == 0 && mSkipPos < mOutputIndex) {
+            if (DEBUG_DICT) {
+                assert(mSkipPos == mOutputIndex - 1);
+            }
+            ++mSkipPos;
+        }
         skip = mSkipPos == mOutputIndex;
+        mSkipping = true;
     }
 
-    if (mTraverseAllNodes || isQuote(c)) {
+    if (mNeedsToTraverseAllNodes || isQuote(c)) {
+        const bool checkProximityChars =
+                !(mSkippedCount > 0 || mExcessivePos >= 0 || mTransposedPos >= 0);
+        // 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.
+        if (checkProximityChars
+                && mInputIndex > 0
+                && mCorrectionStates[mOutputIndex].mProximityMatching
+                && mCorrectionStates[mOutputIndex].mSkipping
+                && mProximityInfo->getMatchedProximityId(
+                        mInputIndex - 1, c, false)
+                        == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) {
+            ++mSkippedCount;
+            --mProximityCount;
+        }
         return processSkipChar(c, isTerminal);
     } else {
         int inputIndexForProximity = mInputIndex;
@@ -186,40 +232,40 @@
             }
         }
 
+        // TODO: sum counters
         const bool checkProximityChars =
-                !(mSkipPos >= 0 || mExcessivePos >= 0 || mTransposedPos >= 0);
+                !(mSkippedCount > 0 || mExcessivePos >= 0 || mTransposedPos >= 0);
         int matchedProximityCharId = mProximityInfo->getMatchedProximityId(
                 inputIndexForProximity, c, checkProximityChars);
 
-        const bool unrelated = ProximityInfo::UNRELATED_CHAR == matchedProximityCharId;
-        if (unrelated) {
-            if (skip) {
+        if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
+            if (skip && mProximityCount == 0) {
                 // Skip this letter and continue deeper
-                mSkippedOutputIndex = mOutputIndex;
+                ++mSkippedCount;
+                return processSkipChar(c, isTerminal);
+            } else if (checkProximityChars
+                    && inputIndexForProximity > 0
+                    && mCorrectionStates[mOutputIndex].mProximityMatching
+                    && mCorrectionStates[mOutputIndex].mSkipping
+                    && mProximityInfo->getMatchedProximityId(
+                            inputIndexForProximity - 1, c, false)
+                                    == ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR) {
+                ++mSkippedCount;
+                --mProximityCount;
                 return processSkipChar(c, isTerminal);
             } else {
                 return UNRELATED;
             }
-        }
-
-        // No need to skip. Finish traversing and increment skipPos.
-        // TODO: Remove this?
-        if (skip) {
-            mWord[mOutputIndex] = c;
-            incrementOutputIndex();
-            return TRAVERSE_ALL_NOT_ON_TERMINAL;
+        } else if (ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
+            // If inputIndex is greater than mInputLength, that means there is no
+            // proximity chars. So, we don't need to check proximity.
+            mMatching = true;
+        } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
+            mProximityMatching = true;
+            incrementProximityCount();
         }
 
         mWord[mOutputIndex] = c;
-        // If inputIndex is greater than mInputLength, that means there is no
-        // proximity chars. So, we don't need to check proximity.
-        if (ProximityInfo::SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
-            charMatched();
-        }
-
-        if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
-            incrementDiffs();
-        }
 
         const bool isSameAsUserTypedLength = mInputLength
                 == getInputIndex() + 1
@@ -232,7 +278,7 @@
         }
         // Start traversing all nodes after the index exceeds the user typed length
         if (isSameAsUserTypedLength) {
-            startTraverseAll();
+            startToTraverseAllNodes();
         }
 
         // Finally, we are ready to go to the next character, the next "virtual node".
@@ -298,26 +344,117 @@
     }
 }
 
+/* static */
+inline static int editDistance(
+        int* editDistanceTable, const unsigned short* input,
+        const int inputLength, const unsigned short* output, const int outputLength) {
+    // dp[li][lo] dp[a][b] = dp[ a * lo + b]
+    int* dp = editDistanceTable;
+    const int li = inputLength + 1;
+    const int lo = outputLength + 1;
+    for (int i = 0; i < li; ++i) {
+        dp[lo * i] = i;
+    }
+    for (int i = 0; i < lo; ++i) {
+        dp[i] = i;
+    }
+
+    for (int i = 0; i < li - 1; ++i) {
+        for (int j = 0; j < lo - 1; ++j) {
+            const uint32_t ci = Dictionary::toBaseLowerCase(input[i]);
+            const uint32_t co = Dictionary::toBaseLowerCase(output[j]);
+            const uint16_t cost = (ci == co) ? 0 : 1;
+            dp[(i + 1) * lo + (j + 1)] = min(dp[i * lo + (j + 1)] + 1,
+                    min(dp[(i + 1) * lo + j] + 1, dp[i * lo + j] + cost));
+            if (li > 0 && lo > 0
+                    && ci == Dictionary::toBaseLowerCase(output[j - 1])
+                    && co == Dictionary::toBaseLowerCase(input[i - 1])) {
+                dp[(i + 1) * lo + (j + 1)] = min(
+                        dp[(i + 1) * lo + (j + 1)], dp[(i - 1) * lo + (j - 1)] + cost);
+            }
+        }
+    }
+
+    if (DEBUG_EDIT_DISTANCE) {
+        LOGI("IN = %d, OUT = %d", inputLength, outputLength);
+        for (int i = 0; i < li; ++i) {
+            for (int j = 0; j < lo; ++j) {
+                LOGI("EDIT[%d][%d], %d", i, j, dp[i * lo + j]);
+            }
+        }
+    }
+    return dp[li * lo - 1];
+}
+
 //////////////////////
 // RankingAlgorithm //
 //////////////////////
 
-int Correction::RankingAlgorithm::calculateFinalFreq(
-        const int inputIndex, const int outputIndex,
-        const int matchCount, const int freq, const bool sameLength,
+/* static */
+int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const int outputIndex,
+        const int freq, const bool sameLength, int* editDistanceTable,
         const Correction* correction) {
-    const int skipPos = correction->getSkipPos();
     const int excessivePos = correction->getExcessivePos();
     const int transposedPos = correction->getTransposedPos();
     const int inputLength = correction->mInputLength;
     const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER;
     const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER;
     const ProximityInfo *proximityInfo = correction->mProximityInfo;
-    const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
+    const int skipCount = correction->mSkippedCount;
+    const int proximityMatchedCount = correction->mProximityCount;
+
+    // TODO: use mExcessiveCount
+    int matchCount = inputLength - correction->mProximityCount - (excessivePos >= 0 ? 1 : 0);
+
+    const unsigned short* word = correction->mWord;
+    const bool skipped = skipCount > 0;
+
+    // ----- TODO: use edit distance here as follows? ---------------------- /
+    //if (!skipped && excessivePos < 0 && transposedPos < 0) {
+    //    const int ed = editDistance(dp, proximityInfo->getInputWord(),
+    //            inputLength, word, outputIndex + 1);
+    //    matchCount = outputIndex + 1 - ed;
+    //    if (ed == 1 && !sameLength) ++matchCount;
+    //}
+    //    const int ed = editDistance(dp, proximityInfo->getInputWord(),
+    //    inputLength, word, outputIndex + 1);
+    //    if (ed == 1 && !sameLength) ++matchCount; ------------------------ /
+    int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount);
 
     // TODO: Demote by edit distance
     int finalFreq = freq * matchWeight;
-    if (skipPos >= 0) {
+    // +1 +11/-12
+    /*if (inputLength == outputIndex && !skipped && excessivePos < 0 && transposedPos < 0) {
+        const int ed = editDistance(dp, proximityInfo->getInputWord(),
+                inputLength, word, outputIndex + 1);
+        if (ed == 1) {
+            multiplyRate(160, &finalFreq);
+        }
+    }*/
+    if (inputLength == outputIndex && excessivePos < 0 && transposedPos < 0
+            && (proximityMatchedCount > 0 || skipped)) {
+        const int ed = editDistance(editDistanceTable, proximityInfo->getPrimaryInputWord(),
+                inputLength, word, outputIndex + 1);
+        if (ed == 1) {
+            multiplyRate(160, &finalFreq);
+        }
+    }
+
+    // TODO: Promote properly?
+    //if (skipCount == 1 && excessivePos < 0 && transposedPos < 0 && inputLength == outputIndex
+    //        && !sameLength) {
+    //    multiplyRate(150, &finalFreq);
+    //}
+    //if (skipCount == 0 && excessivePos < 0 && transposedPos < 0 && inputLength == outputIndex
+    //        && !sameLength) {
+    //    multiplyRate(150, &finalFreq);
+    //}
+    //if (skipCount == 0 && excessivePos < 0 && transposedPos < 0
+    //        && inputLength == outputIndex + 1) {
+    //    multiplyRate(150, &finalFreq);
+    //}
+
+    if (skipped) {
         if (inputLength >= 2) {
             const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE
                     * (10 * inputLength - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X)
@@ -351,10 +488,10 @@
             }
             multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
         }
-        if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
+        if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0) {
             finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
         }
-    } else if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0
+    } else if (sameLength && transposedPos < 0 && !skipped && excessivePos < 0
             && outputIndex > 0) {
         // A word with proximity corrections
         if (DEBUG_DICT) {
@@ -363,13 +500,34 @@
         multiplyIntCapped(typedLetterMultiplier, &finalFreq);
         multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
     }
-    if (DEBUG_DICT) {
+    if (DEBUG_DICT_FULL) {
         LOGI("calc: %d, %d", outputIndex, sameLength);
     }
     if (sameLength) multiplyIntCapped(fullWordMultiplier, &finalFreq);
+
+    // TODO: check excessive count and transposed count
+    /*
+     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
+     */
+    if (matchCount == inputLength && matchCount >= 2 && !skipped
+            && word[matchCount] == word[matchCount - 1]) {
+        multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq);
+    }
+
     return finalFreq;
 }
 
+/* static */
 int Correction::RankingAlgorithm::calcFreqForSplitTwoWords(
         const int firstFreq, const int secondFreq, const Correction* correction) {
     const int spaceProximityPos = correction->mSpaceProximityPos;
diff --git a/native/src/correction.h b/native/src/correction.h
index ae6c7a4..9d385a4 100644
--- a/native/src/correction.h
+++ b/native/src/correction.h
@@ -18,6 +18,7 @@
 #define LATINIME_CORRECTION_H
 
 #include <stdint.h>
+#include "correction_state.h"
 
 #include "defines.h"
 
@@ -39,16 +40,16 @@
     Correction(const int typedLetterMultiplier, const int fullWordMultiplier);
     void initCorrection(
             const ProximityInfo *pi, const int inputLength, const int maxWordLength);
+    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);
     void checkState();
-    void initProcessState(const int matchCount, const int inputIndex, const int outputIndex,
-            const bool traverseAllNodes, const int diffs);
-    void getProcessState(int *matchedCount, int *inputIndex, int *outputIndex,
-            bool *traverseAllNodes, int *diffs);
+    bool initProcessState(const int index);
+
     int getOutputIndex();
     int getInputIndex();
-    bool needsToTraverseAll();
 
     virtual ~Correction();
     int getSpaceProximityPos() const {
@@ -77,52 +78,68 @@
 
     CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal);
 
-    int getDiffs() const {
-        return mDiffs;
+    /////////////////////////
+    // 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;
     }
 private:
-    void charMatched();
-    void incrementInputIndex();
-    void incrementOutputIndex();
-    void startTraverseAll();
+    inline void incrementInputIndex();
+    inline void incrementOutputIndex();
+    inline bool needsToTraverseAllNodes();
+    inline void startToTraverseAllNodes();
+    inline bool isQuote(const unsigned short c);
+    inline CorrectionType processSkipChar(const int32_t c, const bool isTerminal);
 
     // TODO: remove
-
-    void incrementDiffs() {
-        ++mDiffs;
+    inline void incrementProximityCount() {
+        ++mProximityCount;
     }
 
     const int TYPED_LETTER_MULTIPLIER;
     const int FULL_WORD_MULTIPLIER;
-
     const ProximityInfo *mProximityInfo;
 
     int mMaxEditDistance;
     int mMaxDepth;
     int mInputLength;
-    int mSkipPos;
-    int mSkippedOutputIndex;
     int mExcessivePos;
     int mTransposedPos;
     int mSpaceProximityPos;
     int mMissingSpacePos;
-
-    int mMatchedCharCount;
-    int mInputIndex;
-    int mOutputIndex;
     int mTerminalInputIndex;
     int mTerminalOutputIndex;
-    int mDiffs;
-    bool mTraverseAllNodes;
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
+    // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot.
+    int mEditDistanceTable[MAX_WORD_LENGTH_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
 
-    inline bool isQuote(const unsigned short c);
-    inline CorrectionType processSkipChar(const int32_t c, const bool isTerminal);
+    CorrectionState mCorrectionStates[MAX_WORD_LENGTH_INTERNAL];
+
+    // The following member variables are being used as cache values of the correction state.
+    int mOutputIndex;
+    int mInputIndex;
+    int mProximityCount;
+    int mSkippedCount;
+    int mSkipPos;
+    bool mNeedsToTraverseAllNodes;
+    bool mMatching;
+    bool mSkipping;
+    bool mProximityMatching;
 
     class RankingAlgorithm {
     public:
         static int calculateFinalFreq(const int inputIndex, const int depth,
-                const int matchCount, const int freq, const bool sameLength,
+                const int freq, const bool sameLength, int *editDistanceTable,
                 const Correction* correction);
         static int calcFreqForSplitTwoWords(const int firstFreq, const int secondFreq,
                 const Correction* correction);
diff --git a/native/src/correction_state.h b/native/src/correction_state.h
new file mode 100644
index 0000000..267deda
--- /dev/null
+++ b/native/src/correction_state.h
@@ -0,0 +1,57 @@
+/*
+ * 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 mProximityCount;
+    uint8_t mSkippedCount;
+    int8_t mSkipPos; // should be signed
+    bool mMatching;
+    bool mSkipping;
+    bool mProximityMatching;
+    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->mProximityCount = 0;
+    state->mSiblingPos = rootPos;
+    state->mSkippedCount = 0;
+    state->mMatching = false;
+    state->mSkipping = false;
+    state->mProximityMatching = false;
+    state->mNeedsToTraverseAllNodes = traverseAll;
+    state->mSkipPos = -1;
+}
+
+} // namespace latinime
+#endif // LATINIME_CORRECTION_STATE_H
diff --git a/native/src/defines.h b/native/src/defines.h
index 5a5d3ee..c1d08e6 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -94,20 +94,36 @@
 #endif
 #define DEBUG_DICT true
 #define DEBUG_DICT_FULL false
+#define DEBUG_EDIT_DISTANCE false
 #define DEBUG_SHOW_FOUND_WORD DEBUG_DICT_FULL
 #define DEBUG_NODE DEBUG_DICT_FULL
 #define DEBUG_TRACE DEBUG_DICT_FULL
 #define DEBUG_PROXIMITY_INFO true
 
+#define DUMP_WORD(word, length) do { dumpWord(word, length); } while(0)
+
+static char charBuf[50];
+
+static void dumpWord(const unsigned short* word, const int length) {
+    for (int i = 0; i < length; ++i) {
+        charBuf[i] = word[i];
+    }
+    charBuf[length] = 0;
+    LOGI("[ %s ]", charBuf);
+}
+
 #else // FLAG_DBG
 
 #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 DUMP_WORD(word, length)
+
 #endif // FLAG_DBG
 
 #ifndef U_SHORT_MAX
@@ -160,6 +176,7 @@
 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
+#define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105
 
 // This should be greater than or equal to MAX_WORD_LENGTH defined in BinaryDictionary.java
 // This is only used for the size of array. Not to be used in c functions.
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
index d437e25..361bdac 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -68,6 +68,10 @@
 void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength) {
     mInputCodes = inputCodes;
     mInputLength = inputLength;
+    for (int i = 0; i < inputLength; ++i) {
+        mPrimaryInputWord[i] = getPrimaryCharAt(i);
+    }
+    mPrimaryInputWord[inputLength] = 0;
 }
 
 inline const int* ProximityInfo::getProximityCharsAt(const int index) const {
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
index 5034c3b..75fc8fb 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -46,6 +46,10 @@
     ProximityType getMatchedProximityId(
             const int index, const unsigned short c, const bool checkProximityChars) const;
     bool sameAsTyped(const unsigned short *word, int length) const;
+    const unsigned short* getPrimaryInputWord() const {
+        return mPrimaryInputWord;
+    }
+
 private:
     int getStartIndexFromCoordinates(const int x, const int y) const;
     const int MAX_PROXIMITY_CHARS_SIZE;
@@ -58,6 +62,7 @@
     const int *mInputCodes;
     uint32_t *mProximityCharsArray;
     int mInputLength;
+    unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
 };
 
 } // namespace latinime
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index df1a2e2..6bc3505 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -187,20 +187,15 @@
     mCorrection->initCorrection(mProximityInfo, mInputLength, maxDepth);
     PROF_END(0);
 
+    // TODO: remove
     PROF_START(1);
-    getSuggestionCandidates(-1, -1, -1);
+    // Note: This line is intentionally left blank
     PROF_END(1);
 
     PROF_START(2);
     // Suggestion with missing character
-    if (SUGGEST_WORDS_WITH_MISSING_CHARACTER) {
-        for (int i = 0; i < codesSize; ++i) {
-            if (DEBUG_DICT) {
-                LOGI("--- Suggest missing characters %d", i);
-            }
-            getSuggestionCandidates(i, -1, -1);
-        }
-    }
+    LOGI("--- Suggest missing characters");
+    getSuggestionCandidates(0, -1, -1);
     PROF_END(2);
 
     PROF_START(3);
@@ -352,44 +347,28 @@
     int rootPosition = ROOT_POS;
     // Get the number of children of root, then increment the position
     int childCount = Dictionary::getCount(DICT_ROOT, &rootPosition);
-    int depth = 0;
+    int outputIndex = 0;
 
-    mStackChildCount[0] = childCount;
-    mStackTraverseAll[0] = (mInputLength <= 0);
-    mStackInputIndex[0] = 0;
-    mStackDiffs[0] = 0;
-    mStackSiblingPos[0] = rootPosition;
-    mStackOutputIndex[0] = 0;
-    mStackMatchedCount[0] = 0;
+    mCorrection->initCorrectionState(rootPosition, childCount, (mInputLength <= 0));
 
     // Depth first search
-    while (depth >= 0) {
-        if (mStackChildCount[depth] > 0) {
-            --mStackChildCount[depth];
-            int siblingPos = mStackSiblingPos[depth];
+    while (outputIndex >= 0) {
+        if (mCorrection->initProcessState(outputIndex)) {
+            int siblingPos = mCorrection->getTreeSiblingPos(outputIndex);
             int firstChildPos;
-            mCorrection->initProcessState(
-                    mStackMatchedCount[depth], mStackInputIndex[depth], mStackOutputIndex[depth],
-                    mStackTraverseAll[depth], mStackDiffs[depth]);
 
-            // needsToTraverseChildrenNodes should be false
             const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos,
                     mCorrection, &childCount, &firstChildPos, &siblingPos);
             // Update next sibling pos
-            mStackSiblingPos[depth] = siblingPos;
+            mCorrection->setTreeSiblingPos(outputIndex, siblingPos);
+
             if (needsToTraverseChildrenNodes) {
                 // Goes to child node
-                ++depth;
-                mStackChildCount[depth] = childCount;
-                mStackSiblingPos[depth] = firstChildPos;
-
-                mCorrection->getProcessState(&mStackMatchedCount[depth],
-                        &mStackInputIndex[depth], &mStackOutputIndex[depth],
-                        &mStackTraverseAll[depth], &mStackDiffs[depth]);
+                outputIndex = mCorrection->goDownTree(outputIndex, childCount, firstChildPos);
             }
         } else {
             // Goes to parent sibling node
-            --depth;
+            outputIndex = mCorrection->getTreeParentIndex(outputIndex);
         }
     }
 }
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 8bcd7ce..cfe63ff 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -19,6 +19,7 @@
 
 #include <stdint.h>
 #include "correction.h"
+#include "correction_state.h"
 #include "defines.h"
 #include "proximity_info.h"
 
@@ -134,13 +135,9 @@
     // MAX_WORD_LENGTH_INTERNAL must be bigger than MAX_WORD_LENGTH
     unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
 
-    int mStackMatchedCount[MAX_WORD_LENGTH_INTERNAL];
-    int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];
-    bool mStackTraverseAll[MAX_WORD_LENGTH_INTERNAL];
-    int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];
-    int mStackDiffs[MAX_WORD_LENGTH_INTERNAL];
-    int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];
-    int mStackOutputIndex[MAX_WORD_LENGTH_INTERNAL];
+    int mStackChildCount[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+    int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
+    int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];// TODO: remove
 };
 } // namespace latinime
 
diff --git a/tools/makedict/Android.mk b/tools/makedict/Android.mk
index b9fc553..6832b1c 100644
--- a/tools/makedict/Android.mk
+++ b/tools/makedict/Android.mk
@@ -1,5 +1,5 @@
 #
-# Copyright (C) 2009 The Android Open Source Project
+# 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.
@@ -17,8 +17,11 @@
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_SRC_FILES += $(call all-java-files-under,tests)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
+LOCAL_MODULE_TAGS := eng
 LOCAL_MODULE := makedict
+LOCAL_JAVA_LIBRARIES := junit
 
 include $(BUILD_HOST_JAVA_LIBRARY)
 include $(LOCAL_PATH)/etc/Android.mk
diff --git a/tools/makedict/etc/Android.mk b/tools/makedict/etc/Android.mk
index da16286..96a90cb 100644
--- a/tools/makedict/etc/Android.mk
+++ b/tools/makedict/etc/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2009 The Android Open Source Project
+# 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.
@@ -15,6 +15,7 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
+LOCAL_MODULE_TAGS := eng
+
 LOCAL_PREBUILT_EXECUTABLES := makedict
 include $(BUILD_HOST_PREBUILT)
-
diff --git a/tools/makedict/etc/makedict b/tools/makedict/etc/makedict
index 8420d6e..7c1c02e 100755
--- a/tools/makedict/etc/makedict
+++ b/tools/makedict/etc/makedict
@@ -1,5 +1,5 @@
 #!/bin/sh
-# Copyright 2009, The Android Open Source Project
+# 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.
@@ -60,4 +60,4 @@
 
 # need to use "java.ext.dirs" because "-jar" causes classpath to be ignored
 # might need more memory, e.g. -Xmx128M
-exec java -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
+exec java -ea -Djava.ext.dirs="$frameworkdir" -jar "$jarpath" "$@"
diff --git a/tools/makedict/etc/manifest.txt b/tools/makedict/etc/manifest.txt
index aa3a3e8..948609d 100644
--- a/tools/makedict/etc/manifest.txt
+++ b/tools/makedict/etc/manifest.txt
@@ -1 +1 @@
-Main-Class: com.android.tools.dict.MakeBinaryDictionary
+Main-Class: com.android.inputmethod.latin.DictionaryMaker
diff --git a/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
new file mode 100644
index 0000000..92f402d
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java
@@ -0,0 +1,1024 @@
+/*
+ * 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 com.android.inputmethod.latin.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.FusionDictionary.Node;
+import com.android.inputmethod.latin.FusionDictionary.WeightedString;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Reads and writes XML files for a FusionDictionary.
+ *
+ * All the methods in this class are static.
+ */
+public class BinaryDictInputOutput {
+
+    /* Node layout is as follows:
+     *   | addressType                         xx     : mask with MASK_GROUP_ADDRESS_TYPE
+     *                                 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS
+     * f |                                     01 = 1 byte      : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE
+     * l |                                     10 = 2 bytes     : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES
+     * a |                                     11 = 3 bytes     : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES
+     * g | has several chars ?         1 bit, 1 = yes, 0 = no   : FLAG_HAS_MULTIPLE_CHARS
+     * s | has a terminal ?            1 bit, 1 = yes, 0 = no   : FLAG_IS_TERMINAL
+     *   | reserved                    1 bit, 1 = yes, 0 = no
+     *   | has bigrams ?               1 bit, 1 = yes, 0 = no   : FLAG_HAS_BIGRAMS
+     *
+     * c | IF FLAG_HAS_MULTIPLE_CHARS
+     * h |   char, char, char, char    n * (1 or 3 bytes) : use CharGroupInfo for i/o helpers
+     * a |   end                       1 byte, = 0
+     * r | ELSE
+     * s |   char                      1 or 3 bytes
+     *   | END
+     *
+     * f |
+     * r | IF FLAG_IS_TERMINAL
+     * e |   frequency                 1 byte
+     * q |
+     *
+     * c | IF 00 = FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = addressType
+     * h |   // nothing
+     * i | ELSIF 01 = FLAG_GROUP_ADDRESS_TYPE_ONEBYTE == addressType
+     * l |   children address, 1 byte
+     * d | ELSIF 10 = FLAG_GROUP_ADDRESS_TYPE_TWOBYTES == addressType
+     * r |   children address, 2 bytes
+     * e | ELSE // 11 = FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = addressType
+     * n |   children address, 3 bytes
+     * A | END
+     * d
+     * dress
+     *
+     *   | IF FLAG_IS_TERMINAL && FLAG_HAS_BIGRAMS
+     *   | bigrams address list
+     *
+     * Char format is:
+     * 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).
+     *
+     * bigram and shortcut address list is:
+     * <flags> = | hasNext = 1 bit, 1 = yes, 0 = no     : FLAG_ATTRIBUTE_HAS_NEXT
+     *           | addressSign = 1 bit,                 : FLAG_ATTRIBUTE_OFFSET_NEGATIVE
+     *           |                      1 = must take -address, 0 = must take +address
+     *           |                         xx : mask with MASK_ATTRIBUTE_ADDRESS_TYPE
+     *           | addressFormat = 2 bits, 00 = unused  : FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE
+     *           |                         01 = 1 byte  : FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE
+     *           |                         10 = 2 bytes : FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES
+     *           |                         11 = 3 bytes : FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES
+     *           | 4 bits : frequency         : mask with FLAG_ATTRIBUTE_FREQUENCY
+     * <address> | IF (01 == FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE == addressFormat)
+     *           |   read 1 byte, add top 4 bits
+     *           | ELSIF (10 == FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES == addressFormat)
+     *           |   read 2 bytes, add top 4 bits
+     *           | ELSE // 11 == FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES == addressFormat
+     *           |   read 3 bytes, add top 4 bits
+     *           | END
+     *           | if (FLAG_ATTRIBUTE_OFFSET_NEGATIVE) then address = -address
+     * if (FLAG_ATTRIBUTE_HAS_NET) goto bigram_and_shortcut_address_list_is
+     *
+     */
+
+    private static final int MAGIC_NUMBER = 0x78B1;
+    private static final int VERSION = 1;
+    private static final int MAXIMUM_SUPPORTED_VERSION = VERSION;
+    // No options yet, reserved for future use.
+    private static final int OPTIONS = 0;
+
+    // TODO: Make this value adaptative to content data, store it in the header, and
+    // use it in the reading code.
+    private static final int MAX_WORD_LENGTH = 48;
+
+    private static final int MASK_GROUP_ADDRESS_TYPE = 0xC0;
+    private static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00;
+    private static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40;
+    private static final int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80;
+    private static final int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0;
+
+    private static final int FLAG_HAS_MULTIPLE_CHARS = 0x20;
+
+    private static final int FLAG_IS_TERMINAL = 0x10;
+    private static final int FLAG_HAS_BIGRAMS = 0x04;
+
+    private static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80;
+    private static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40;
+    private static final int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30;
+    private static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10;
+    private static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20;
+    private static final int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30;
+    private static final int FLAG_ATTRIBUTE_FREQUENCY = 0x0F;
+
+    private static final int GROUP_CHARACTERS_TERMINATOR = 0x1F;
+
+    private static final int GROUP_COUNT_SIZE = 1;
+    private static final int GROUP_TERMINATOR_SIZE = 1;
+    private static final int GROUP_FLAGS_SIZE = 1;
+    private static final int GROUP_FREQUENCY_SIZE = 1;
+    private static final int GROUP_MAX_ADDRESS_SIZE = 3;
+    private static final int GROUP_ATTRIBUTE_FLAGS_SIZE = 1;
+    private static final int GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE = 3;
+
+    private static final int NO_CHILDREN_ADDRESS = Integer.MIN_VALUE;
+    private static final int INVALID_CHARACTER = -1;
+
+    // Limiting to 127 for upward compatibility
+    // TODO: implement a scheme to be able to shoot 256 chargroups in a node
+    private static final int MAX_CHARGROUPS_IN_A_NODE = 127;
+
+    private static final int MAX_TERMINAL_FREQUENCY = 255;
+
+    /**
+     * A class grouping utility function for our specific character encoding.
+     */
+    private static class CharEncoding {
+
+        private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20;
+        private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF;
+
+        /**
+         * Helper method to find out whether this code fits on one byte
+         */
+        private static boolean fitsOnOneByte(int character) {
+            return character >= MINIMAL_ONE_BYTE_CHARACTER_VALUE
+                    && character <= MAXIMAL_ONE_BYTE_CHARACTER_VALUE;
+        }
+
+        /**
+         * Compute the size of a character given its character code.
+         *
+         * Char format is:
+         * 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).
+         *
+         * @param character the character code.
+         * @return the size in binary encoded-form, either 1 or 3 bytes.
+         */
+        private static int getCharSize(int character) {
+            // See char encoding in FusionDictionary.java
+            if (fitsOnOneByte(character)) return 1;
+            if (INVALID_CHARACTER == character) return 1;
+            return 3;
+        }
+
+        /**
+         * Compute the byte size of a character array.
+         */
+        private static int getCharArraySize(final int[] chars) {
+            int size = 0;
+            for (int character : chars) size += getCharSize(character);
+            return size;
+        }
+
+        /**
+         * Writes a char array to a byte buffer.
+         *
+         * @param characters the character array to write.
+         * @param buffer the byte buffer to write to.
+         * @param index the index in buffer to write the character array to.
+         * @return the index after the last character.
+         */
+        private static int writeCharArray(int[] characters, byte[] buffer, int index) {
+            for (int character : characters) {
+                if (1 == getCharSize(character)) {
+                    buffer[index++] = (byte)character;
+                } else {
+                    buffer[index++] = (byte)(0xFF & (character >> 16));
+                    buffer[index++] = (byte)(0xFF & (character >> 8));
+                    buffer[index++] = (byte)(0xFF & character);
+                }
+            }
+            return index;
+        }
+
+        /**
+         * Reads a character from the file.
+         *
+         * This follows the character format documented earlier in this source file.
+         *
+         * @param source the file, positioned over an encoded character.
+         * @return the character code.
+         */
+        private static int readChar(RandomAccessFile source) throws IOException {
+            int character = source.readUnsignedByte();
+            if (!fitsOnOneByte(character)) {
+                if (GROUP_CHARACTERS_TERMINATOR == character)
+                    return INVALID_CHARACTER;
+                character <<= 16;
+                character += source.readUnsignedShort();
+            }
+            return character;
+        }
+    }
+
+    /**
+     * Compute the binary size of the character array in a group
+     *
+     * If only one character, this is the size of this character. If many, it's the sum of their
+     * sizes + 1 byte for the terminator.
+     *
+     * @param group the group
+     * @return the size of the char array, including the terminator if any
+     */
+    private static int getGroupCharactersSize(CharGroup group) {
+        int size = CharEncoding.getCharArraySize(group.mChars);
+        if (group.hasSeveralChars()) size += GROUP_TERMINATOR_SIZE;
+        return size;
+    }
+
+    /**
+     * Compute the maximum size of a CharGroup, assuming 3-byte addresses for everything.
+     *
+     * @param group the CharGroup to compute the size of.
+     * @return the maximum size of the group.
+     */
+    private static int getCharGroupMaximumSize(CharGroup group) {
+        int size = getGroupCharactersSize(group) + GROUP_FLAGS_SIZE;
+        // If terminal, one byte for the frequency
+        if (group.isTerminal()) size += GROUP_FREQUENCY_SIZE;
+        size += GROUP_MAX_ADDRESS_SIZE; // For children address
+        if (null != group.mBigrams) {
+            for (WeightedString bigram : group.mBigrams) {
+                size += GROUP_ATTRIBUTE_FLAGS_SIZE + GROUP_ATTRIBUTE_MAX_ADDRESS_SIZE;
+            }
+        }
+        return size;
+    }
+
+    /**
+     * Compute the maximum size of a node, assuming 3-byte addresses for everything, and caches
+     * it in the 'actualSize' member of the node.
+     *
+     * @param node the node to compute the maximum size of.
+     */
+    private static void setNodeMaximumSize(Node node) {
+        int size = GROUP_COUNT_SIZE;
+        for (CharGroup g : node.mData) {
+            final int groupSize = getCharGroupMaximumSize(g);
+            g.mCachedSize = groupSize;
+            size += groupSize;
+        }
+        node.mCachedSize = size;
+    }
+
+    /**
+     * Helper method to hide the actual value of the no children address.
+     */
+    private static boolean hasChildrenAddress(int address) {
+        return NO_CHILDREN_ADDRESS != address;
+    }
+
+    /**
+     * Compute the size, in bytes, that an address will occupy.
+     *
+     * This can be used either for children addresses (which are always positive) or for
+     * attribute, which may be positive or negative but
+     * store their sign bit separately.
+     *
+     * @param address the address
+     * @return the byte size.
+     */
+    private static int getByteSize(int address) {
+        assert(address < 0x1000000);
+        if (!hasChildrenAddress(address)) {
+            return 0;
+        } else if (Math.abs(address) < 0x100) {
+            return 1;
+        } else if (Math.abs(address) < 0x10000) {
+            return 2;
+        } else {
+            return 3;
+        }
+    }
+    // End utility methods.
+
+    // This method is responsible for finding a nice ordering of the nodes that favors run-time
+    // cache performance and dictionary size.
+    /* package for tests */ static ArrayList<Node> flattenTree(Node root) {
+        final int treeSize = FusionDictionary.countCharGroups(root);
+        MakedictLog.i("Counted nodes : " + treeSize);
+        final ArrayList<Node> flatTree = new ArrayList<Node>(treeSize);
+        return flattenTreeInner(flatTree, root);
+    }
+
+    private static ArrayList<Node> flattenTreeInner(ArrayList<Node> list, Node node) {
+        // Removing the node is necessary if the tails are merged, because we would then
+        // add the same node several times when we only want it once. A number of places in
+        // the code also depends on any node being only once in the list.
+        // Merging tails can only be done if there are no attributes. Searching for attributes
+        // in LatinIME code depends on a total breadth-first ordering, which merging tails
+        // breaks. If there are no attributes, it should be fine (and reduce the file size)
+        // to merge tails, and the following step would be necessary.
+        // If eventually the code runs on Android, searching through the whole array each time
+        // may be a performance concern.
+        list.remove(node);
+        list.add(node);
+        final ArrayList<CharGroup> branches = node.mData;
+        final int nodeSize = branches.size();
+        for (CharGroup group : branches) {
+            if (null != group.mChildren) flattenTreeInner(list, group.mChildren);
+        }
+        return list;
+    }
+
+    /**
+     * Finds the absolute address of a word in the dictionary.
+     *
+     * @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.
+     */
+    private static int findAddressOfWord(final FusionDictionary dict, final String word) {
+        return FusionDictionary.findWordInTree(dict.mRoot, word).mCachedAddress;
+    }
+
+    /**
+     * Computes the actual node size, based on the cached addresses of the children nodes.
+     *
+     * Each node stores its tentative address. During dictionary address computing, these
+     * are not final, but they can be used to compute the node size (the node size depends
+     * on the address of the children because the number of bytes necessary to store an
+     * address depends on its numeric value.
+     *
+     * @param node the node to compute the size of.
+     * @param dict the dictionary in which the word/attributes are to be found.
+     */
+    private static void computeActualNodeSize(Node node, FusionDictionary dict) {
+        int size = GROUP_COUNT_SIZE;
+        for (CharGroup group : node.mData) {
+            int groupSize = GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
+            if (group.isTerminal()) groupSize += GROUP_FREQUENCY_SIZE;
+            if (null != group.mChildren) {
+                final int offsetBasePoint= groupSize + node.mCachedAddress + size;
+                final int offset = group.mChildren.mCachedAddress - offsetBasePoint;
+                groupSize += getByteSize(offset);
+            }
+            if (null != group.mBigrams) {
+                for (WeightedString bigram : group.mBigrams) {
+                    final int offsetBasePoint = groupSize + node.mCachedAddress + size
+                            + GROUP_FLAGS_SIZE;
+                    final int addressOfBigram = findAddressOfWord(dict, bigram.mWord);
+                    final int offset = addressOfBigram - offsetBasePoint;
+                    groupSize += getByteSize(offset) + GROUP_FLAGS_SIZE;
+                }
+            }
+            group.mCachedSize = groupSize;
+            size += groupSize;
+        }
+        node.mCachedSize = size;
+    }
+
+    /**
+     * Computes the byte size of a list of nodes and updates each node cached position.
+     *
+     * @param flatNodes the array of nodes.
+     * @return the byte size of the entire stack.
+     */
+    private static int stackNodes(ArrayList<Node> flatNodes) {
+        int nodeOffset = 0;
+        for (Node n : flatNodes) {
+            n.mCachedAddress = nodeOffset;
+            int groupOffset = 0;
+            for (CharGroup g : n.mData) {
+                g.mCachedAddress = GROUP_COUNT_SIZE + nodeOffset + groupOffset;
+                groupOffset += g.mCachedSize;
+            }
+            if (groupOffset + GROUP_COUNT_SIZE != n.mCachedSize) {
+                throw new RuntimeException("Bug : Stored and computed node size differ");
+            }
+            nodeOffset += n.mCachedSize;
+        }
+        return nodeOffset;
+    }
+
+    /**
+     * 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
+     * so that they can be written into a file. It determines the smallest size each of the
+     * nodes can be given the addresses of its children and attributes, and store that into
+     * each node.
+     * The order of the node is given by the order of the array. This method makes no effort
+     * to find a good order; it only mechanically computes the size this order results in.
+     *
+     * @param dict the dictionary
+     * @param flatNodes the ordered array of nodes
+     * @return the same array it was passed. The nodes have been updated for address and size.
+     */
+    private static ArrayList<Node> computeAddresses(FusionDictionary dict,
+            ArrayList<Node> flatNodes) {
+        // First get the worst sizes and offsets
+        for (Node n : flatNodes) setNodeMaximumSize(n);
+        final int offset = stackNodes(flatNodes);
+
+        MakedictLog.i("Compressing the array addresses. Original size : " + offset);
+        MakedictLog.i("(Recursively seen size : " + offset + ")");
+
+        int passes = 0;
+        boolean changesDone = false;
+        do {
+            changesDone = false;
+            for (Node n : flatNodes) {
+                final int oldNodeSize = n.mCachedSize;
+                computeActualNodeSize(n, dict);
+                final int newNodeSize = n.mCachedSize;
+                if (oldNodeSize < newNodeSize) throw new RuntimeException("Increased size ?!");
+                if (oldNodeSize != newNodeSize) changesDone = true;
+            }
+            stackNodes(flatNodes);
+            ++passes;
+        } while (changesDone);
+
+        final Node lastNode = flatNodes.get(flatNodes.size() - 1);
+        MakedictLog.i("Compression complete in " + passes + " passes.");
+        MakedictLog.i("After address compression : "
+                + (lastNode.mCachedAddress + lastNode.mCachedSize));
+
+        return flatNodes;
+    }
+
+    /**
+     * Sanity-checking method.
+     *
+     * This method checks an array of node for juxtaposition, that is, it will do
+     * nothing if each node's cached address is actually the previous node's address
+     * plus the previous node's size.
+     * If this is not the case, it will throw an exception.
+     *
+     * @param array the array node to check
+     */
+    private static void checkFlatNodeArray(ArrayList<Node> array) {
+        int offset = 0;
+        int index = 0;
+        for (Node n : array) {
+            if (n.mCachedAddress != offset) {
+                throw new RuntimeException("Wrong address for node " + index
+                        + " : expected " + offset + ", got " + n.mCachedAddress);
+            }
+            ++index;
+            offset += n.mCachedSize;
+        }
+    }
+
+    /**
+     * Helper method to write a variable-size address to a file.
+     *
+     * @param buffer the buffer to write to.
+     * @param index the index in the buffer to write the address to.
+     * @param address the address to write.
+     * @return the size in bytes the address actually took.
+     */
+    private static int writeVariableAddress(byte[] buffer, int index, int address) {
+        switch (getByteSize(address)) {
+        case 1:
+            buffer[index++] = (byte)address;
+            return 1;
+        case 2:
+            buffer[index++] = (byte)(0xFF & (address >> 8));
+            buffer[index++] = (byte)(0xFF & address);
+            return 2;
+        case 3:
+            buffer[index++] = (byte)(0xFF & (address >> 16));
+            buffer[index++] = (byte)(0xFF & (address >> 8));
+            buffer[index++] = (byte)(0xFF & address);
+            return 3;
+        case 0:
+            return 0;
+        default:
+            throw new RuntimeException("Address " + address + " has a strange size");
+        }
+    }
+
+    private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress,
+            final int childrenOffset) {
+        byte flags = 0;
+        if (group.mChars.length > 1) flags |= FLAG_HAS_MULTIPLE_CHARS;
+        if (group.mFrequency >= 0) {
+            flags |= FLAG_IS_TERMINAL;
+        }
+        if (null != group.mChildren) {
+            switch (getByteSize(childrenOffset)) {
+             case 1:
+                 flags |= FLAG_GROUP_ADDRESS_TYPE_ONEBYTE;
+                 break;
+             case 2:
+                 flags |= FLAG_GROUP_ADDRESS_TYPE_TWOBYTES;
+                 break;
+             case 3:
+                 flags |= FLAG_GROUP_ADDRESS_TYPE_THREEBYTES;
+                 break;
+             default:
+                 throw new RuntimeException("Node with a strange address");
+             }
+        }
+        if (null != group.mBigrams) flags |= FLAG_HAS_BIGRAMS;
+        return flags;
+    }
+
+    /**
+     * Makes the flag value for an attribute.
+     *
+     * @param more whether there are more attributes after this one.
+     * @param offset the offset of the attribute.
+     * @param frequency the frequency of the attribute, 0..15
+     * @return the flags
+     */
+    private static final int makeAttributeFlags(final boolean more, final int offset,
+            final int frequency) {
+        int bigramFlags = (more ? FLAG_ATTRIBUTE_HAS_NEXT : 0)
+                + (offset < 0 ? FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0);
+        switch (getByteSize(offset)) {
+        case 1:
+            bigramFlags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+            break;
+        case 2:
+            bigramFlags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+            break;
+        case 3:
+            bigramFlags |= FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+            break;
+        default:
+            throw new RuntimeException("Strange offset size");
+        }
+        bigramFlags += frequency & FLAG_ATTRIBUTE_FREQUENCY;
+        return bigramFlags;
+    }
+
+    /**
+     * Write a node to memory. The node is expected to have its final position cached.
+     *
+     * This can be an empty map, but the more is inside the faster the lookups will be. It can
+     * be carried on as long as nodes do not move.
+     *
+     * @param dict the dictionary the node is a part of (for relative offsets).
+     * @param buffer the memory buffer to write to.
+     * @param node the node to write.
+     * @return the address of the END of the node.
+     */
+    private static int writePlacedNode(FusionDictionary dict, byte[] buffer, Node node) {
+        int index = node.mCachedAddress;
+
+        final int size = node.mData.size();
+        if (size > MAX_CHARGROUPS_IN_A_NODE)
+            throw new RuntimeException("A node has a group count over 127 (" + size + ").");
+
+        buffer[index++] = (byte)size;
+        int groupAddress = index;
+        for (int i = 0; i < size; ++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");
+            groupAddress += GROUP_FLAGS_SIZE + getGroupCharactersSize(group);
+            // Sanity checks.
+            if (group.mFrequency > MAX_TERMINAL_FREQUENCY) {
+                throw new RuntimeException("A node has a frequency > " + MAX_TERMINAL_FREQUENCY
+                        + " : " + group.mFrequency);
+            }
+            if (group.mFrequency >= 0) groupAddress += GROUP_FREQUENCY_SIZE;
+            final int childrenOffset = null == group.mChildren
+                    ? NO_CHILDREN_ADDRESS : group.mChildren.mCachedAddress - groupAddress;
+            byte flags = makeCharGroupFlags(group, groupAddress, childrenOffset);
+            buffer[index++] = flags;
+            index = CharEncoding.writeCharArray(group.mChars, buffer, index);
+            if (group.hasSeveralChars()) {
+                buffer[index++] = GROUP_CHARACTERS_TERMINATOR;
+            }
+            if (group.mFrequency >= 0) {
+                buffer[index++] = (byte) group.mFrequency;
+            }
+            final int shift = writeVariableAddress(buffer, index, childrenOffset);
+            index += shift;
+            groupAddress += shift;
+
+            // Write bigrams
+            if (null != group.mBigrams) {
+                int remainingBigrams = group.mBigrams.size();
+                for (WeightedString bigram : group.mBigrams) {
+                    boolean more = remainingBigrams > 1;
+                    final int addressOfBigram = findAddressOfWord(dict, bigram.mWord);
+                    ++groupAddress;
+                    final int offset = addressOfBigram - groupAddress;
+                    int bigramFlags = makeAttributeFlags(more, offset, bigram.mFrequency);
+                    buffer[index++] = (byte)bigramFlags;
+                    final int bigramShift = writeVariableAddress(buffer, index, Math.abs(offset));
+                    index += bigramShift;
+                    groupAddress += bigramShift;
+                    --remainingBigrams;
+                }
+            }
+
+        }
+        if (index != node.mCachedAddress + node.mCachedSize) throw new RuntimeException(
+                "Not the same size : written "
+                + (index - node.mCachedAddress) + " bytes out of a node that should have "
+                + node.mCachedSize + " bytes");
+        return index;
+    }
+
+    /**
+     * Dumps a collection of useful statistics about a node array.
+     *
+     * This prints purely informative stuff, like the total estimated file size, the
+     * number of nodes, of character groups, the repartition of each address size, etc
+     *
+     * @param nodes the node array.
+     */
+    private static void showStatistics(ArrayList<Node> nodes) {
+        int firstTerminalAddress = Integer.MAX_VALUE;
+        int lastTerminalAddress = Integer.MIN_VALUE;
+        int size = 0;
+        int charGroups = 0;
+        int maxGroups = 0;
+        int maxRuns = 0;
+        for (Node n : nodes) {
+            if (maxGroups < n.mData.size()) maxGroups = n.mData.size();
+            for (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.mCachedAddress + n.mCachedSize > size) size = n.mCachedAddress + n.mCachedSize;
+        }
+        final int[] groupCounts = new int[maxGroups + 1];
+        final int[] runCounts = new int[maxRuns + 1];
+        for (Node n : nodes) {
+            ++groupCounts[n.mData.size()];
+            for (CharGroup cg : n.mData) {
+                ++runCounts[cg.mChars.length];
+            }
+        }
+
+        MakedictLog.i("Statistics:\n"
+                + "  total file size " + size + "\n"
+                + "  " + nodes.size() + " nodes\n"
+                + "  " + charGroups + " groups (" + ((float)charGroups / nodes.size())
+                        + " groups per node)\n"
+                + "  first terminal at " + firstTerminalAddress + "\n"
+                + "  last terminal at " + lastTerminalAddress + "\n"
+                + "  Group stats : max = " + maxGroups);
+        for (int i = 0; i < groupCounts.length; ++i) {
+            MakedictLog.i("    " + i + " : " + groupCounts[i]);
+        }
+        MakedictLog.i("  Character run stats : max = " + maxRuns);
+        for (int i = 0; i < runCounts.length; ++i) {
+            MakedictLog.i("    " + i + " : " + runCounts[i]);
+        }
+    }
+
+    /**
+     * Dumps a FusionDictionary to a file.
+     *
+     * This is the public entry point to write a dictionary to a file.
+     *
+     * @param destination the stream to write the binary data to.
+     * @param dict the dictionary to write.
+     */
+    public static void writeDictionaryBinary(OutputStream destination, FusionDictionary dict)
+            throws IOException {
+
+        // Addresses are limited to 3 bytes, so we'll just make a 16MB buffer. Since addresses
+        // can be relative to each node, the structure itself is not limited to 16MB at all, but
+        // I doubt this will ever be shot. If it is, deciding the order of the nodes becomes
+        // a quite complicated problem, because though the dictionary itself does not have a
+        // size limit, each node must still be within 16MB of all its children and parents.
+        // As long as this is ensured, the dictionary file may grow to any size.
+        // Anyway, to make a dictionary bigger than 16MB just increase the size of this buffer.
+        final byte[] buffer = new byte[1 << 24];
+        int index = 0;
+
+        // Magic number in big-endian order.
+        buffer[index++] = (byte) (0xFF & (MAGIC_NUMBER >> 8));
+        buffer[index++] = (byte) (0xFF & MAGIC_NUMBER);
+        // Dictionary version.
+        buffer[index++] = (byte) (0xFF & VERSION);
+        // Options flags
+        buffer[index++] = (byte) (0xFF & (OPTIONS >> 8));
+        buffer[index++] = (byte) (0xFF & OPTIONS);
+
+        // Should we include the locale and title of the dictionary ?
+
+        destination.write(buffer, 0, index);
+        index = 0;
+
+        // Leave the choice of the optimal node order to the flattenTree function.
+        MakedictLog.i("Flattening the tree...");
+        ArrayList<Node> flatNodes = flattenTree(dict.mRoot);
+
+        MakedictLog.i("Computing addresses...");
+        computeAddresses(dict, flatNodes);
+        MakedictLog.i("Checking array...");
+        checkFlatNodeArray(flatNodes);
+
+        MakedictLog.i("Writing file...");
+        int dataEndOffset = 0;
+        for (Node n : flatNodes) {
+            dataEndOffset = writePlacedNode(dict, buffer, n);
+        }
+
+        showStatistics(flatNodes);
+
+        destination.write(buffer, 0, dataEndOffset);
+
+        destination.close();
+        MakedictLog.i("Done");
+    }
+
+
+    // Input methods: Read a binary dictionary to memory.
+    // readDictionaryBinary is the public entry point for them.
+
+    static final int[] characterBuffer = new int[MAX_WORD_LENGTH];
+    private static CharGroupInfo readCharGroup(RandomAccessFile source,
+            final int originalGroupAddress) throws IOException {
+        int addressPointer = originalGroupAddress;
+        final int flags = source.readUnsignedByte();
+        ++addressPointer;
+        final int characters[];
+        if (0 != (flags & FLAG_HAS_MULTIPLE_CHARS)) {
+            int index = 0;
+            int character = CharEncoding.readChar(source);
+            addressPointer += CharEncoding.getCharSize(character);
+            while (-1 != character) {
+                characterBuffer[index++] = character;
+                character = CharEncoding.readChar(source);
+                addressPointer += CharEncoding.getCharSize(character);
+            }
+            characters = Arrays.copyOfRange(characterBuffer, 0, index);
+        } else {
+            final int character = CharEncoding.readChar(source);
+            addressPointer += CharEncoding.getCharSize(character);
+            characters = new int[] { character };
+        }
+        final int frequency;
+        if (0 != (FLAG_IS_TERMINAL & flags)) {
+            ++addressPointer;
+            frequency = source.readUnsignedByte();
+        } else {
+            frequency = CharGroup.NOT_A_TERMINAL;
+        }
+        int childrenAddress = addressPointer;
+        switch (flags & MASK_GROUP_ADDRESS_TYPE) {
+        case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE:
+            childrenAddress += source.readUnsignedByte();
+            addressPointer += 1;
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_TWOBYTES:
+            childrenAddress += source.readUnsignedShort();
+            addressPointer += 2;
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_THREEBYTES:
+            childrenAddress += (source.readUnsignedByte() << 16) + source.readUnsignedShort();
+            addressPointer += 3;
+            break;
+        case FLAG_GROUP_ADDRESS_TYPE_NOADDRESS:
+        default:
+            childrenAddress = NO_CHILDREN_ADDRESS;
+            break;
+        }
+        ArrayList<PendingAttribute> bigrams = null;
+        if (0 != (flags & FLAG_HAS_BIGRAMS)) {
+            bigrams = new ArrayList<PendingAttribute>();
+            boolean more = true;
+            while (more) {
+                int bigramFlags = source.readUnsignedByte();
+                ++addressPointer;
+                more = (0 != (bigramFlags & FLAG_ATTRIBUTE_HAS_NEXT));
+                final int sign = 0 == (bigramFlags & FLAG_ATTRIBUTE_OFFSET_NEGATIVE) ? 1 : -1;
+                int bigramAddress = addressPointer;
+                switch (bigramFlags & MASK_ATTRIBUTE_ADDRESS_TYPE) {
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE:
+                    bigramAddress += sign * source.readUnsignedByte();
+                    addressPointer += 1;
+                    break;
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES:
+                    bigramAddress += sign * source.readUnsignedShort();
+                    addressPointer += 2;
+                    break;
+                case FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES:
+                    final int offset = ((source.readUnsignedByte() << 16)
+                            + source.readUnsignedShort());
+                    bigramAddress += sign * offset;
+                    addressPointer += 3;
+                    break;
+                default:
+                    throw new RuntimeException("Has attribute with no address");
+                }
+                bigrams.add(new PendingAttribute(bigramFlags & FLAG_ATTRIBUTE_FREQUENCY,
+                        bigramAddress));
+            }
+        }
+        return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency,
+                childrenAddress, bigrams);
+    }
+
+    /**
+     * Finds, as a string, the word at the address passed as an argument.
+     *
+     * @param source the file to read from.
+     * @param headerSize the size of the header.
+     * @param address the address to seek.
+     * @return the word, as a string.
+     * @throws IOException if the file can't be read.
+     */
+    private static String getWordAtAddress(RandomAccessFile source, long headerSize,
+            int address) throws IOException {
+        final long originalPointer = source.getFilePointer();
+        source.seek(headerSize);
+        final int count = source.readUnsignedByte();
+        int groupOffset = 1; // 1 for the group count
+        final StringBuilder builder = new StringBuilder();
+        String result = null;
+
+        CharGroupInfo last = null;
+        for (int i = count - 1; i >= 0; --i) {
+            CharGroupInfo info = readCharGroup(source, groupOffset);
+            groupOffset = info.mEndAddress;
+            if (info.mOriginalAddress == address) {
+                builder.append(new String(info.mCharacters, 0, info.mCharacters.length));
+                result = builder.toString();
+                break; // and return
+            }
+            if (hasChildrenAddress(info.mChildrenAddress)) {
+                if (info.mChildrenAddress > address) {
+                    if (null == last) continue;
+                    builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
+                    source.seek(last.mChildrenAddress + headerSize);
+                    groupOffset = last.mChildrenAddress + 1;
+                    i = source.readUnsignedByte();
+                    last = null;
+                    continue;
+                }
+                last = info;
+            }
+            if (0 == i && hasChildrenAddress(last.mChildrenAddress)) {
+                builder.append(new String(last.mCharacters, 0, last.mCharacters.length));
+                source.seek(last.mChildrenAddress + headerSize);
+                groupOffset = last.mChildrenAddress + 1;
+                i = source.readUnsignedByte();
+                last = null;
+                continue;
+            }
+        }
+        source.seek(originalPointer);
+        return result;
+    }
+
+    /**
+     * Reads a single node from a binary file.
+     *
+     * This methods reads the file at the current position of its file pointer. A node is
+     * fully expected to start at the current position.
+     * This will recursively read other nodes into the structure, populating the reverse
+     * maps on the fly and using them to keep track of already read nodes.
+     *
+     * @param source the data file, correctly positioned at the start of a node.
+     * @param headerSize the size, in bytes, of the file header.
+     * @param reverseNodeMap a mapping from addresses to already read nodes.
+     * @param reverseGroupMap a mapping from addresses to already read character groups.
+     * @return the read node with all his children already read.
+     */
+    private static Node readNode(RandomAccessFile source, long headerSize,
+            Map<Integer, Node> reverseNodeMap, Map<Integer, CharGroup> reverseGroupMap)
+            throws IOException {
+        final int nodeOrigin = (int)(source.getFilePointer() - headerSize);
+        final int count = source.readUnsignedByte();
+        final ArrayList<CharGroup> nodeContents = new ArrayList<CharGroup>();
+        int groupOffset = nodeOrigin + 1; // 1 byte for the group count
+        for (int i = count; i > 0; --i) {
+            CharGroupInfo info = readCharGroup(source, groupOffset);
+            ArrayList<WeightedString> bigrams = null;
+            if (null != info.mBigrams) {
+                bigrams = new ArrayList<WeightedString>();
+                for (PendingAttribute bigram : info.mBigrams) {
+                    final String word = getWordAtAddress(source, headerSize, bigram.mAddress);
+                    bigrams.add(new WeightedString(word, bigram.mFrequency));
+                }
+            }
+            if (hasChildrenAddress(info.mChildrenAddress)) {
+                Node children = reverseNodeMap.get(info.mChildrenAddress);
+                if (null == children) {
+                    final long currentPosition = source.getFilePointer();
+                    source.seek(info.mChildrenAddress + headerSize);
+                    children = readNode(source, headerSize, reverseNodeMap, reverseGroupMap);
+                    source.seek(currentPosition);
+                }
+                nodeContents.add(
+                        new CharGroup(info.mCharacters, bigrams, info.mFrequency,
+                        children));
+            } else {
+                nodeContents.add(
+                        new CharGroup(info.mCharacters, bigrams, info.mFrequency));
+            }
+            groupOffset = info.mEndAddress;
+        }
+        final Node node = new Node(nodeContents);
+        node.mCachedAddress = nodeOrigin;
+        reverseNodeMap.put(node.mCachedAddress, node);
+        return node;
+    }
+
+    /**
+     * Reads a random access file and returns the memory representation of the dictionary.
+     *
+     * This high-level method takes a binary file and reads its contents, populating a
+     * FusionDictionary structure. The optional dict argument is an existing dictionary to
+     * which words from the file should be added. If it is null, a new dictionary is created.
+     *
+     * @param source the file to read.
+     * @param dict an optional dictionary to add words to, or null.
+     * @return the created (or merged) dictionary.
+     */
+    public static FusionDictionary readDictionaryBinary(RandomAccessFile source,
+            FusionDictionary dict) throws IOException, UnsupportedFormatException {
+        // Check magic number
+        final int magic = source.readUnsignedShort();
+        if (MAGIC_NUMBER != magic) {
+            throw new UnsupportedFormatException("The magic number in this file does not match "
+                    + "the expected value");
+        }
+
+        // Check file version
+        final int version = source.readUnsignedByte();
+        if (version > MAXIMUM_SUPPORTED_VERSION) {
+            throw new UnsupportedFormatException("This file has version " + version
+                    + ", but this implementation does not support versions above "
+                    + MAXIMUM_SUPPORTED_VERSION);
+        }
+
+        // Read options
+        source.readUnsignedShort();
+
+        long headerSize = source.getFilePointer();
+        Map<Integer, Node> reverseNodeMapping = new TreeMap<Integer, Node>();
+        Map<Integer, CharGroup> reverseGroupMapping = new TreeMap<Integer, CharGroup>();
+        final Node root = readNode(source, headerSize, reverseNodeMapping, reverseGroupMapping);
+
+        FusionDictionary newDict = new FusionDictionary(root,
+                new FusionDictionary.DictionaryOptions());
+        if (null != dict) {
+            for (Word w : dict) {
+                newDict.add(w.mWord, w.mFrequency, w.mBigrams);
+            }
+        }
+
+        return newDict;
+    }
+
+    /**
+     * Basic test to find out whether the file is a binary dictionary or not.
+     *
+     * Concretely this only tests the magic number.
+     *
+     * @param filename The name of the file to test.
+     * @return true if it's a binary dictionary, false otherwise
+     */
+    public static boolean isBinaryDictionary(String filename) {
+        try {
+            RandomAccessFile f = new RandomAccessFile(filename, "r");
+            return MAGIC_NUMBER == f.readUnsignedShort();
+        } catch (FileNotFoundException e) {
+            return false;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java b/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
new file mode 100644
index 0000000..6badfd1
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.util.ArrayList;
+
+/**
+ * Raw char group info straight out of a file. This will contain numbers for addresses.
+ */
+public class CharGroupInfo {
+
+    public final int mOriginalAddress;
+    public final int mEndAddress;
+    public final int mFlags;
+    public final int[] mCharacters;
+    public final int mFrequency;
+    public final int mChildrenAddress;
+    public final ArrayList<PendingAttribute> mBigrams;
+
+    public CharGroupInfo(final int originalAddress, final int endAddress, final int flags,
+            final int[] characters, final int frequency, final int childrenAddress,
+            final ArrayList<PendingAttribute> bigrams) {
+        mOriginalAddress = originalAddress;
+        mEndAddress = endAddress;
+        mFlags = flags;
+        mCharacters = characters;
+        mFrequency = frequency;
+        mChildrenAddress = childrenAddress;
+        mBigrams = bigrams;
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java b/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
new file mode 100644
index 0000000..1ba0107
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java
@@ -0,0 +1,256 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.LinkedList;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.xml.sax.SAXException;
+
+/**
+ * Main class/method for DictionaryMaker.
+ */
+public class DictionaryMaker {
+
+    static class Arguments {
+        private final static String OPTION_VERSION_2 = "-2";
+        private final static String OPTION_INPUT_SOURCE = "-s";
+        private final static String OPTION_INPUT_BIGRAM_XML = "-b";
+        private final static String OPTION_OUTPUT_BINARY = "-d";
+        private final static String OPTION_OUTPUT_XML = "-x";
+        private final static String OPTION_HELP = "-h";
+        public final String mInputBinary;
+        public final String mInputUnigramXml;
+        public final String mInputBigramXml;
+        public final String mOutputBinary;
+        public final String mOutputXml;
+
+        private void checkIntegrity() {
+            checkHasExactlyOneInput();
+            checkHasAtLeastOneOutput();
+        }
+
+        private void checkHasExactlyOneInput() {
+            if (null == mInputUnigramXml && null == mInputBinary) {
+                throw new RuntimeException("No input file specified");
+            } else if (null != mInputUnigramXml && null != mInputBinary) {
+                throw new RuntimeException("Both input XML and binary specified");
+            } else if (null != mInputBinary && null != mInputBigramXml) {
+                throw new RuntimeException("Cannot specify a binary input and a separate bigram "
+                        + "file");
+            }
+        }
+
+        private void checkHasAtLeastOneOutput() {
+            if (null == mOutputBinary && null == mOutputXml) {
+                throw new RuntimeException("No output specified");
+            }
+        }
+
+        private void displayHelp() {
+            MakedictLog.i("Usage: makedict "
+                    + "[-s <unigrams.xml> [-b <bigrams.xml>] | -s <binary input>] "
+                    + " [-d <binary output>] [-x <xml output>] [-2]\n"
+                    + "\n"
+                    + "  Converts a source dictionary file to one or several outputs.\n"
+                    + "  Source can be an XML file, with an optional XML bigrams file, or a\n"
+                    + "  binary dictionary file.\n"
+                    + "  Both binary and XML outputs are supported. Both can be output at\n"
+                    + "  the same time but outputting several files of the same type is not\n"
+                    + "  supported.");
+        }
+
+        public Arguments(String[] argsArray) {
+            final LinkedList<String> args = new LinkedList<String>(Arrays.asList(argsArray));
+            if (args.isEmpty()) {
+                displayHelp();
+            }
+            String inputBinary = null;
+            String inputUnigramXml = null;
+            String inputBigramXml = null;
+            String outputBinary = null;
+            String outputXml = null;
+
+            while (!args.isEmpty()) {
+                final String arg = args.get(0);
+                args.remove(0);
+                if (arg.charAt(0) == '-') {
+                    if (OPTION_VERSION_2.equals(arg)) {
+                        // Do nothing, this is the default
+                    } else if (OPTION_HELP.equals(arg)) {
+                        displayHelp();
+                    } else {
+                        // All these options need an argument
+                        if (args.isEmpty()) {
+                            throw new RuntimeException("Option " + arg + " requires an argument");
+                        }
+                        String filename = args.get(0);
+                        args.remove(0);
+                        if (OPTION_INPUT_SOURCE.equals(arg)) {
+                            if (BinaryDictInputOutput.isBinaryDictionary(filename)) {
+                                inputBinary = filename;
+                            } else {
+                                inputUnigramXml = filename;
+                            }
+                        } else if (OPTION_INPUT_BIGRAM_XML.equals(arg)) {
+                            inputBigramXml = filename;
+                        } else if (OPTION_OUTPUT_BINARY.equals(arg)) {
+                            outputBinary = filename;
+                        } else if (OPTION_OUTPUT_XML.equals(arg)) {
+                            outputXml = filename;
+                        }
+                    }
+                } else {
+                    if (null == inputBinary && null == inputUnigramXml) {
+                        if (BinaryDictInputOutput.isBinaryDictionary(arg)) {
+                            inputBinary = arg;
+                        } else {
+                            inputUnigramXml = arg;
+                        }
+                    } else if (null == outputBinary) {
+                        outputBinary = arg;
+                    } else {
+                        throw new RuntimeException("Several output binary files specified");
+                    }
+                }
+            }
+
+            mInputBinary = inputBinary;
+            mInputUnigramXml = inputUnigramXml;
+            mInputBigramXml = inputBigramXml;
+            mOutputBinary = outputBinary;
+            mOutputXml = outputXml;
+            checkIntegrity();
+        }
+    }
+
+    public static void main(String[] args)
+            throws FileNotFoundException, ParserConfigurationException, SAXException, IOException,
+            UnsupportedFormatException {
+        final Arguments parsedArgs = new Arguments(args);
+        FusionDictionary dictionary = readInputFromParsedArgs(parsedArgs);
+        writeOutputToParsedArgs(parsedArgs, dictionary);
+    }
+
+    /**
+     * Invoke the right input method according to args.
+     *
+     * @param args the parsed command line arguments.
+     * @return the read dictionary.
+     */
+    private static FusionDictionary readInputFromParsedArgs(final Arguments args)
+            throws IOException, UnsupportedFormatException, ParserConfigurationException,
+            SAXException, FileNotFoundException {
+        if (null != args.mInputBinary) {
+            return readBinaryFile(args.mInputBinary);
+        } else if (null != args.mInputUnigramXml) {
+            return readXmlFile(args.mInputUnigramXml, args.mInputBigramXml);
+        } else {
+            throw new RuntimeException("No input file specified");
+        }
+    }
+
+    /**
+     * Read a dictionary from the name of a binary file.
+     *
+     * @param binaryFilename the name of the file in the binary dictionary format.
+     * @return the read dictionary.
+     * @throws FileNotFoundException if the file can't be found
+     * @throws IOException if the input file can't be read
+     * @throws UnsupportedFormatException if the binary file is not in the expected format
+     */
+    private static FusionDictionary readBinaryFile(final String binaryFilename)
+            throws FileNotFoundException, IOException, UnsupportedFormatException {
+        final RandomAccessFile inputFile = new RandomAccessFile(binaryFilename, "r");
+        return BinaryDictInputOutput.readDictionaryBinary(inputFile, null);
+    }
+
+    /**
+     * Read a dictionary from a unigram XML file, and optionally a bigram XML file.
+     *
+     * @param unigramXmlFilename the name of the unigram XML file. May not be null.
+     * @param bigramXmlFilename the name of the bigram XML file. Pass null if there are no bigrams.
+     * @return the read dictionary.
+     * @throws FileNotFoundException if one of the files can't be found
+     * @throws SAXException if one or more of the XML files is not well-formed
+     * @throws IOException if one the input files can't be read
+     * @throws ParserConfigurationException if the system can't create a SAX parser
+     */
+    private static FusionDictionary readXmlFile(final String unigramXmlFilename,
+            final String bigramXmlFilename) throws FileNotFoundException, SAXException,
+            IOException, ParserConfigurationException {
+        final FileInputStream unigrams = new FileInputStream(new File(unigramXmlFilename));
+        final FileInputStream bigrams = null == bigramXmlFilename ? null :
+                new FileInputStream(new File(bigramXmlFilename));
+        return XmlDictInputOutput.readDictionaryXml(unigrams, bigrams);
+    }
+
+    /**
+     * Invoke the right output method according to args.
+     *
+     * This will write the passed dictionary to the file(s) passed in the command line arguments.
+     * @param args the parsed arguments.
+     * @param dict the file to output.
+     * @throws FileNotFoundException if one of the output files can't be created.
+     * @throws IOException if one of the output files can't be written to.
+     */
+    private static void writeOutputToParsedArgs(final Arguments args, final FusionDictionary dict)
+            throws FileNotFoundException, IOException {
+        if (null != args.mOutputBinary) {
+            writeBinaryDictionary(args.mOutputBinary, dict);
+        }
+        if (null != args.mOutputXml) {
+            writeXmlDictionary(args.mOutputXml, dict);
+        }
+    }
+
+    /**
+     * Write the dictionary in binary format to the specified filename.
+     *
+     * @param outputFilename the name of the file to write to.
+     * @param dict the dictionary to write.
+     * @throws FileNotFoundException if the output file can't be created.
+     * @throws IOException if the output file can't be written to.
+     */
+    private static void writeBinaryDictionary(final String outputFilename,
+            final FusionDictionary dict) throws FileNotFoundException, IOException {
+        final File outputFile = new File(outputFilename);
+        BinaryDictInputOutput.writeDictionaryBinary(new FileOutputStream(outputFilename), dict);
+    }
+
+    /**
+     * Write the dictionary in XML format to the specified filename.
+     *
+     * @param outputFilename the name of the file to write to.
+     * @param dict the dictionary to write.
+     * @throws FileNotFoundException if the output file can't be created.
+     * @throws IOException if the output file can't be written to.
+     */
+    private static void writeXmlDictionary(final String outputFilename,
+            final FusionDictionary dict) throws FileNotFoundException, IOException {
+        XmlDictInputOutput.writeDictionaryXml(new FileWriter(outputFilename), dict);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
new file mode 100644
index 0000000..031f35d
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java
@@ -0,0 +1,602 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A dictionary that can fusion heads and tails of words for more compression.
+ */
+public class FusionDictionary implements Iterable<Word> {
+
+    /**
+     * A node of the dictionary, containing several CharGroups.
+     *
+     * A node is but an ordered array of CharGroups, which essentially contain all the
+     * real information.
+     * This class also contains fields to cache size and address, to help with binary
+     * generation.
+     */
+    public static class Node {
+        ArrayList<CharGroup> mData;
+        // To help with binary generation
+        int mCachedSize;
+        int mCachedAddress;
+        public Node() {
+            mData = new ArrayList<CharGroup>();
+            mCachedSize = Integer.MIN_VALUE;
+            mCachedAddress = Integer.MIN_VALUE;
+        }
+        public Node(ArrayList<CharGroup> data) {
+            mData = data;
+            mCachedSize = Integer.MIN_VALUE;
+            mCachedAddress = Integer.MIN_VALUE;
+        }
+    }
+
+    /**
+     * A string with a frequency.
+     *
+     * This represents an "attribute", that is either a bigram or a shortcut.
+     */
+    public static class WeightedString {
+        final String mWord;
+        final int mFrequency;
+        public WeightedString(String word, int frequency) {
+            mWord = word;
+            mFrequency = frequency;
+        }
+    }
+
+    /**
+     * A group of characters, with a frequency, shortcuts, bigrams, and children.
+     *
+     * This is the central class of the in-memory representation. A CharGroup is what can
+     * be seen as a traditional "trie node", except it can hold several characters at the
+     * same time. A CharGroup essentially represents one or several characters in the middle
+     * of the trie trie; as such, it can be a terminal, and it can have children.
+     * In this in-memory representation, whether the CharGroup is a terminal or not is represented
+     * in the frequency, where NOT_A_TERMINAL (= -1) means this is not a terminal and any other
+     * value is the frequency of this terminal. A terminal may have non-null shortcuts and/or
+     * bigrams, but a non-terminal may not. Moreover, children, if present, are null.
+     */
+    public static class CharGroup {
+        public static final int NOT_A_TERMINAL = -1;
+        final int mChars[];
+        final ArrayList<WeightedString> mBigrams;
+        final int mFrequency; // NOT_A_TERMINAL == mFrequency indicates this is not a terminal.
+        Node mChildren;
+        // The two following members to help with binary generation
+        int mCachedSize;
+        int mCachedAddress;
+
+        public CharGroup(final int[] chars,
+                final ArrayList<WeightedString> bigrams, final int frequency) {
+            mChars = chars;
+            mFrequency = frequency;
+            mBigrams = bigrams;
+            mChildren = null;
+        }
+
+        public CharGroup(final int[] chars,
+                final ArrayList<WeightedString> bigrams, final int frequency, final Node children) {
+            mChars = chars;
+            mFrequency = frequency;
+            mBigrams = bigrams;
+            mChildren = children;
+        }
+
+        public void addChild(CharGroup n) {
+            if (null == mChildren) {
+                mChildren = new Node();
+            }
+            mChildren.mData.add(n);
+        }
+
+        public boolean isTerminal() {
+            return NOT_A_TERMINAL != mFrequency;
+        }
+
+        public boolean hasSeveralChars() {
+            assert(mChars.length > 0);
+            return 1 < mChars.length;
+        }
+    }
+
+    /**
+     * Options global to the dictionary.
+     *
+     * There are no options at the moment, so this class is empty.
+     */
+    public static class DictionaryOptions {
+    }
+
+
+    public final DictionaryOptions mOptions;
+    public final Node mRoot;
+
+    public FusionDictionary() {
+        mOptions = new DictionaryOptions();
+        mRoot = new Node();
+    }
+
+    public FusionDictionary(final Node root, final DictionaryOptions options) {
+        mRoot = root;
+        mOptions = options;
+    }
+
+    /**
+     * Helper method to convert a String to an int array.
+     */
+    static private int[] getCodePoints(String word) {
+        final int wordLength = word.length();
+        int[] array = new int[word.codePointCount(0, wordLength)];
+        for (int i = 0; i < wordLength; ++i) {
+            array[i] = word.codePointAt(i);
+        }
+        return array;
+    }
+
+    /**
+     * Helper method to add a word as a string.
+     *
+     * This method adds a word to the dictionary with the given frequency. Optional
+     * lists of bigrams and shortcuts can be passed here. For each word inside,
+     * they will be added to the dictionary as necessary.
+     *
+     * @param word the word to add.
+     * @param frequency the frequency of the word, in the range [0..255].
+     * @param bigrams a list of bigrams, or null.
+     */
+    public void add(String word, int frequency, ArrayList<WeightedString> bigrams) {
+        if (null != bigrams) {
+            for (WeightedString bigram : bigrams) {
+                final CharGroup t = findWordInTree(mRoot, bigram.mWord);
+                if (null == t) {
+                    add(getCodePoints(bigram.mWord), 0, null);
+                }
+            }
+        }
+        add(getCodePoints(word), frequency, bigrams);
+    }
+
+    /**
+     * Sanity check for a node.
+     *
+     * This method checks that all CharGroups in a node are ordered as expected.
+     * If they are, nothing happens. If they aren't, an exception is thrown.
+     */
+    private void checkStack(Node node) {
+        ArrayList<CharGroup> stack = node.mData;
+        int lastValue = -1;
+        for (int i = 0; i < stack.size(); ++i) {
+            int currentValue = stack.get(i).mChars[0];
+            if (currentValue <= lastValue)
+                throw new RuntimeException("Invalid stack");
+            else
+                lastValue = currentValue;
+        }
+    }
+
+    /**
+     * Add a word to this dictionary.
+     *
+     * The bigrams, if any, have to be in the dictionary already. If they aren't,
+     * an exception is thrown.
+     *
+     * @param word the word, as an int array.
+     * @param frequency the frequency of the word, in the range [0..255].
+     * @param bigrams an optional list of bigrams for this word (null if none).
+     */
+    private void add(int[] word, int frequency, ArrayList<WeightedString> bigrams) {
+        assert(frequency >= 0 && frequency <= 255);
+        Node currentNode = mRoot;
+        int charIndex = 0;
+
+        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) {
+            currentGroup = currentNode.mData.get(nodeIndex);
+            differentCharIndex = compareArrays(currentGroup.mChars, word, charIndex) ;
+            if (ARRAYS_ARE_EQUAL != differentCharIndex
+                    && differentCharIndex < currentGroup.mChars.length) break;
+            if (null == currentGroup.mChildren) break;
+            charIndex += currentGroup.mChars.length;
+            if (charIndex >= word.length) break;
+            currentNode = currentGroup.mChildren;
+            nodeIndex = findIndexOfChar(currentNode, word[charIndex]);
+        }
+
+        if (-1 == nodeIndex) {
+            // No node at this point to accept the word. Create one.
+            final int insertionIndex = findInsertionIndex(currentNode, word[charIndex]);
+            final CharGroup newGroup = new CharGroup(
+                    Arrays.copyOfRange(word, charIndex, word.length), bigrams, frequency);
+            currentNode.mData.add(insertionIndex, newGroup);
+            checkStack(currentNode);
+        } else {
+            // There is a word with a common prefix.
+            if (differentCharIndex == currentGroup.mChars.length) {
+                if (charIndex + differentCharIndex >= word.length) {
+                    // The new word is a prefix of an existing word, but the node on which it
+                    // should end already exists as is.
+                    if (currentGroup.mFrequency > 0) {
+                        throw new RuntimeException("Such a word already exists in the dictionary : "
+                                + new String(word, 0, word.length));
+                    } else {
+                        final CharGroup newNode = new CharGroup(currentGroup.mChars,
+                                bigrams, frequency, currentGroup.mChildren);
+                        currentNode.mData.set(nodeIndex, newNode);
+                        checkStack(currentNode);
+                    }
+                } else {
+                    // The new word matches the full old word and extends past it.
+                    // We only have to create a new node and add it to the end of this.
+                    final CharGroup newNode = new CharGroup(
+                            Arrays.copyOfRange(word, charIndex + differentCharIndex, word.length),
+                                    bigrams, frequency);
+                    currentGroup.mChildren = new Node();
+                    currentGroup.mChildren.mData.add(newNode);
+                }
+            } else {
+                if (0 == differentCharIndex) {
+                    // Exact same word. Check the frequency is 0 or -1, and update.
+                    if (0 != frequency) {
+                        if (0 < currentGroup.mFrequency) {
+                            throw new RuntimeException("This word already exists with frequency "
+                                    + currentGroup.mFrequency + " : "
+                                    + new String(word, 0, word.length));
+                        }
+                        final CharGroup newGroup = new CharGroup(word,
+                                currentGroup.mBigrams, frequency);
+                        currentNode.mData.set(nodeIndex, newGroup);
+                    }
+                } else {
+                    // Partial prefix match only. We have to replace the current node with a node
+                    // containing the current prefix and create two new ones for the tails.
+                    Node newChildren = new Node();
+                    final CharGroup newOldWord = new CharGroup(
+                            Arrays.copyOfRange(currentGroup.mChars, differentCharIndex,
+                                    currentGroup.mChars.length),
+                            currentGroup.mBigrams, currentGroup.mFrequency, currentGroup.mChildren);
+                    newChildren.mData.add(newOldWord);
+
+                    final CharGroup newParent;
+                    if (charIndex + differentCharIndex >= word.length) {
+                        newParent = new CharGroup(
+                                Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
+                                        bigrams, frequency, newChildren);
+                    } else {
+                        newParent = new CharGroup(
+                                Arrays.copyOfRange(currentGroup.mChars, 0, differentCharIndex),
+                                        null, -1, newChildren);
+                        final CharGroup newWord = new CharGroup(
+                                Arrays.copyOfRange(word, charIndex + differentCharIndex,
+                                        word.length), bigrams, frequency);
+                        final int addIndex = word[charIndex + differentCharIndex]
+                                > currentGroup.mChars[differentCharIndex] ? 1 : 0;
+                        newChildren.mData.add(addIndex, newWord);
+                    }
+                    currentNode.mData.set(nodeIndex, newParent);
+                }
+                checkStack(currentNode);
+            }
+        }
+    }
+
+    /**
+     * Custom comparison of two int arrays taken to contain character codes.
+     *
+     * This method compares the two arrays passed as an argument in a lexicographic way,
+     * with an offset in the dst string.
+     * This method does NOT test for the first character. It is taken to be equal.
+     * I repeat: this method starts the comparison at 1 <> dstOffset + 1.
+     * The index where the strings differ is returned. ARRAYS_ARE_EQUAL = 0 is returned if the
+     * strings are equal. This works BECAUSE we don't look at the first character.
+     *
+     * @param src the left-hand side string of the comparison.
+     * @param dst the right-hand side string of the comparison.
+     * @param dstOffset the offset in the right-hand side string.
+     * @return the index at which the strings differ, or ARRAYS_ARE_EQUAL = 0 if they don't.
+     */
+    private static int ARRAYS_ARE_EQUAL = 0;
+    private static int compareArrays(final int[] src, final int[] dst, int dstOffset) {
+        // We do NOT test the first char, because we come from a method that already
+        // tested it.
+        for (int i = 1; i < src.length; ++i) {
+            if (dstOffset + i >= dst.length) return i;
+            if (src[i] != dst[dstOffset + i]) return i;
+        }
+        if (dst.length > src.length) return src.length;
+        return ARRAYS_ARE_EQUAL;
+    }
+
+    /**
+     * Helper class that compares and sorts two chargroups according to their
+     * first element only. I repeat: ONLY the first element is considered, the rest
+     * is ignored.
+     * This comparator imposes orderings that are inconsistent with equals.
+     */
+    static private class CharGroupComparator implements java.util.Comparator {
+        public int compare(Object o1, Object o2) {
+            final CharGroup c1 = (CharGroup)o1;
+            final CharGroup c2 = (CharGroup)o2;
+            if (c1.mChars[0] == c2.mChars[0]) return 0;
+            return c1.mChars[0] < c2.mChars[0] ? -1 : 1;
+        }
+        public boolean equals(Object o) {
+            return o instanceof CharGroupComparator;
+        }
+    }
+    final static private CharGroupComparator CHARGROUP_COMPARATOR = new CharGroupComparator();
+
+    /**
+     * Finds the insertion index of a character within a node.
+     */
+    private static int findInsertionIndex(final Node node, int character) {
+        final List data = node.mData;
+        final CharGroup reference = new CharGroup(new int[] { character }, null, 0);
+        int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
+        return result >= 0 ? result : -result - 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.
+     */
+    private static int CHARACTER_NOT_FOUND = -1;
+    private static int findIndexOfChar(final Node node, int character) {
+        final int insertionIndex = findInsertionIndex(node, character);
+        if (node.mData.size() <= insertionIndex) return CHARACTER_NOT_FOUND;
+        return character == node.mData.get(insertionIndex).mChars[0] ? insertionIndex
+                : CHARACTER_NOT_FOUND;
+    }
+
+    /**
+     * Helper method to find a word in a given branch.
+     */
+    public static CharGroup findWordInTree(Node node, final String s) {
+        int index = 0;
+        final StringBuilder checker = new StringBuilder();
+
+        CharGroup currentGroup;
+        do {
+            int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
+            if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
+            currentGroup = node.mData.get(indexOfGroup);
+            checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
+            index += currentGroup.mChars.length;
+            if (index < s.length()) {
+                node = currentGroup.mChildren;
+            }
+        } while (null != node && index < s.length());
+
+        if (!s.equals(checker.toString())) return null;
+        return currentGroup;
+    }
+
+    /**
+     * Recursively count the number of character groups in a given branch of the trie.
+     *
+     * @param node the parent node.
+     * @return the number of char groups in all the branch under this node.
+     */
+    public static int countCharGroups(final Node node) {
+        final int nodeSize = node.mData.size();
+        int size = nodeSize;
+        for (int i = nodeSize - 1; i >= 0; --i) {
+            CharGroup group = node.mData.get(i);
+            if (null != group.mChildren)
+                size += countCharGroups(group.mChildren);
+        }
+        return size;
+    }
+
+    /**
+     * Recursively count the number of nodes in a given branch of the trie.
+     *
+     * @param node the node to count.
+     * @result the number of nodes in this branch.
+     */
+    public static int countNodes(final Node node) {
+        int size = 1;
+        for (int i = node.mData.size() - 1; i >= 0; --i) {
+            CharGroup group = node.mData.get(i);
+            if (null != group.mChildren)
+                size += countNodes(group.mChildren);
+        }
+        return size;
+    }
+
+    // Historically, the tails of the words were going to be merged to save space.
+    // However, that would prevent the code to search for a specific address in log(n)
+    // time so this was abandoned.
+    // The code is still of interest as it does add some compression to any dictionary
+    // that has no need for attributes. Implementations that does not read attributes should be
+    // able to read a dictionary with merged tails.
+    // Also, the following code does support frequencies, as in, it will only merges
+    // tails that share the same frequency. Though it would result in the above loss of
+    // performance while searching by address, it is still technically possible to merge
+    // tails that contain attributes, but this code does not take that into account - it does
+    // not compare attributes and will merge terminals with different attributes regardless.
+    public void mergeTails() {
+        MakedictLog.i("Do not merge tails");
+        return;
+
+//        MakedictLog.i("Merging nodes. Number of nodes : " + countNodes(root));
+//        MakedictLog.i("Number of groups : " + countCharGroups(root));
+//
+//        final HashMap<String, ArrayList<Node>> repository =
+//                  new HashMap<String, ArrayList<Node>>();
+//        mergeTailsInner(repository, root);
+//
+//        MakedictLog.i("Number of different pseudohashes : " + repository.size());
+//        int size = 0;
+//        for (ArrayList<Node> a : repository.values()) {
+//            size += a.size();
+//        }
+//        MakedictLog.i("Number of nodes after merge : " + (1 + size));
+//        MakedictLog.i("Recursively seen nodes : " + countNodes(root));
+    }
+
+    // The following methods are used by the deactivated mergeTails()
+//   private static boolean isEqual(Node a, Node b) {
+//       if (null == a && null == b) return true;
+//       if (null == a || null == b) return false;
+//       if (a.data.size() != b.data.size()) return false;
+//       final int size = a.data.size();
+//       for (int i = size - 1; i >= 0; --i) {
+//           CharGroup aGroup = a.data.get(i);
+//           CharGroup bGroup = b.data.get(i);
+//           if (aGroup.frequency != bGroup.frequency) return false;
+//           if (aGroup.alternates == null && bGroup.alternates != null) return false;
+//           if (aGroup.alternates != null && !aGroup.equals(bGroup.alternates)) return false;
+//           if (!Arrays.equals(aGroup.chars, bGroup.chars)) return false;
+//           if (!isEqual(aGroup.children, bGroup.children)) return false;
+//       }
+//       return true;
+//   }
+
+//   static private HashMap<String, ArrayList<Node>> mergeTailsInner(
+//           final HashMap<String, ArrayList<Node>> map, final Node node) {
+//       final ArrayList<CharGroup> branches = node.data;
+//       final int nodeSize = branches.size();
+//       for (int i = 0; i < nodeSize; ++i) {
+//           CharGroup group = branches.get(i);
+//           if (null != group.children) {
+//               String pseudoHash = getPseudoHash(group.children);
+//               ArrayList<Node> similarList = map.get(pseudoHash);
+//               if (null == similarList) {
+//                   similarList = new ArrayList<Node>();
+//                   map.put(pseudoHash, similarList);
+//               }
+//               boolean merged = false;
+//               for (Node similar : similarList) {
+//                   if (isEqual(group.children, similar)) {
+//                       group.children = similar;
+//                       merged = true;
+//                       break;
+//                   }
+//               }
+//               if (!merged) {
+//                   similarList.add(group.children);
+//               }
+//               mergeTailsInner(map, group.children);
+//           }
+//       }
+//       return map;
+//   }
+
+//  private static String getPseudoHash(final Node node) {
+//      StringBuilder s = new StringBuilder();
+//      for (CharGroup g : node.data) {
+//          s.append(g.frequency);
+//          for (int ch : g.chars){
+//              s.append(Character.toChars(ch));
+//          }
+//      }
+//      return s.toString();
+//  }
+
+    /**
+     * Iterator to walk through a dictionary.
+     *
+     * This is purely for convenience.
+     */
+    public static class DictionaryIterator implements Iterator<Word> {
+
+        private static class Position {
+            public Iterator<CharGroup> pos;
+            public int length;
+            public Position(ArrayList<CharGroup> groups) {
+                pos = groups.iterator();
+                length = 0;
+            }
+        }
+        final StringBuilder mCurrentString;
+        final LinkedList<Position> mPositions;
+
+        public DictionaryIterator(ArrayList<CharGroup> root) {
+            mCurrentString = new StringBuilder();
+            mPositions = new LinkedList<Position>();
+            final Position rootPos = new Position(root);
+            mPositions.add(rootPos);
+        }
+
+        @Override
+        public boolean hasNext() {
+            for (Position p : mPositions) {
+                if (p.pos.hasNext()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public Word next() {
+            Position currentPos = mPositions.getLast();
+            mCurrentString.setLength(mCurrentString.length() - currentPos.length);
+
+            do {
+                if (currentPos.pos.hasNext()) {
+                    final CharGroup currentGroup = currentPos.pos.next();
+                    currentPos.length = currentGroup.mChars.length;
+                    for (int i : currentGroup.mChars)
+                        mCurrentString.append(Character.toChars(i));
+                    if (null != currentGroup.mChildren) {
+                        currentPos = new Position(currentGroup.mChildren.mData);
+                        mPositions.addLast(currentPos);
+                    }
+                    if (currentGroup.mFrequency >= 0)
+                        return new Word(mCurrentString.toString(), currentGroup.mFrequency,
+                                currentGroup.mBigrams);
+                } else {
+                    mPositions.removeLast();
+                    currentPos = mPositions.getLast();
+                    mCurrentString.setLength(mCurrentString.length() - mPositions.getLast().length);
+                }
+            } while(true);
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException("Unsupported yet");
+        }
+
+    }
+
+    /**
+     * Method to return an iterator.
+     *
+     * This method enables Java's enhanced for loop. With this you can have a FusionDictionary x
+     * and say : for (Word w : x) {}
+     */
+    @Override
+    public Iterator<Word> iterator() {
+        return new DictionaryIterator(mRoot.mData);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java b/tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java
new file mode 100644
index 0000000..badb2ff
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Wrapper to redirect log events to the right output medium.
+ */
+public class MakedictLog {
+
+    private static void print(String message) {
+        System.out.println(message);
+    }
+
+    public static void d(String message) {
+        print(message);
+    }
+    public static void e(String message) {
+        print(message);
+    }
+    public static void i(String message) {
+        print(message);
+    }
+    public static void w(String message) {
+        print(message);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java b/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
new file mode 100644
index 0000000..e502021
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * A not-yet-resolved attribute.
+ *
+ * An attribute is either a bigram or an shortcut.
+ * All instances of this class are always immutable.
+ */
+public class PendingAttribute {
+    public final int mFrequency;
+    public final int mAddress;
+    public PendingAttribute(final int frequency, final int address) {
+        mFrequency = frequency;
+        mAddress = address;
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java b/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
new file mode 100644
index 0000000..511ad56
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * Simple exception thrown when a file format is not recognized.
+ */
+public class UnsupportedFormatException extends Exception {
+    public UnsupportedFormatException(String description) {
+        super(description);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/Word.java b/tools/makedict/src/com/android/inputmethod/latin/Word.java
new file mode 100644
index 0000000..916165a
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/Word.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.android.inputmethod.latin.FusionDictionary.WeightedString;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class for a word with a frequency.
+ *
+ * This is chiefly used to iterate a dictionary.
+ */
+public class Word implements Comparable<Word> {
+    final String mWord;
+    final int mFrequency;
+    final ArrayList<WeightedString> mBigrams;
+
+    public Word(String word, int frequency, ArrayList<WeightedString> bigrams) {
+        mWord = word;
+        mFrequency = frequency;
+        mBigrams = bigrams;
+    }
+
+    /**
+     * Three-way comparison.
+     *
+     * A Word x is greater than a word y if x has a higher frequency. If they have the same
+     * frequency, they are sorted in lexicographic order.
+     */
+    @Override
+    public int compareTo(Word w) {
+        if (mFrequency < w.mFrequency) return 1;
+        if (mFrequency > w.mFrequency) return -1;
+        return mWord.compareTo(w.mWord);
+    }
+
+    /**
+     * Equality test.
+     *
+     * Words are equal if they have the same frequency, the same spellings, and the same
+     * attributes.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Word)) return false;
+        Word w = (Word)o;
+        return mFrequency == w.mFrequency && mWord.equals(w.mWord)
+                && mBigrams.equals(w.mBigrams);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
new file mode 100644
index 0000000..096bfd1
--- /dev/null
+++ b/tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java
@@ -0,0 +1,215 @@
+/*
+ * 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 com.android.inputmethod.latin.FusionDictionary.WeightedString;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.TreeSet;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Reads and writes XML files for a FusionDictionary.
+ *
+ * All functions in this class are static.
+ */
+public class XmlDictInputOutput {
+
+    private static final String WORD_TAG = "w";
+    private static final String BIGRAM_TAG = "bigram";
+    private static final String FREQUENCY_ATTR = "f";
+    private static final String WORD_ATTR = "word";
+
+    /**
+     * SAX handler for a unigram XML file.
+     */
+    static private class UnigramHandler extends DefaultHandler {
+        // Parser states
+        private static final int NONE = 0;
+        private static final int START = 1;
+        private static final int WORD = 2;
+        private static final int BIGRAM = 4;
+        private static final int END = 5;
+        private static final int UNKNOWN = 6;
+
+        final FusionDictionary mDictionary;
+        int mState; // the state of the parser
+        int mFreq; // the currently read freq
+        final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
+
+        /**
+         * Create the handler.
+         *
+         * @param dict the dictionary to construct.
+         * @param bigrams the bigrams as a map. This may be empty, but may not be null.
+         */
+        public UnigramHandler(FusionDictionary dict,
+                HashMap<String, ArrayList<WeightedString>> bigrams) {
+            mDictionary = dict;
+            mBigramsMap = bigrams;
+            mState = START;
+            mFreq = 0;
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs) {
+            if (WORD_TAG.equals(localName)) {
+                mState = WORD;
+                for (int attrIndex = 0; attrIndex < attrs.getLength(); ++attrIndex) {
+                    final String attrName = attrs.getLocalName(attrIndex);
+                    if (FREQUENCY_ATTR.equals(attrName)) {
+                        mFreq = Integer.parseInt(attrs.getValue(attrIndex));
+                    }
+                }
+            } else {
+                mState = UNKNOWN;
+            }
+        }
+
+        @Override
+        public void characters(char[] ch, int start, int length) {
+            if (WORD == mState) {
+                final String word = String.copyValueOf(ch, start, length);
+                mDictionary.add(word, mFreq, mBigramsMap.get(word));
+            }
+        }
+
+        @Override
+        public void endElement(String uri, String localName, String qName) {
+            if (WORD == mState) mState = START;
+        }
+    }
+
+    /**
+     * SAX handler for a bigram XML file.
+     */
+    static private class BigramHandler extends DefaultHandler {
+        private final static String BIGRAM_W1_TAG = "bi";
+        private final static String BIGRAM_W2_TAG = "w";
+        private final static String BIGRAM_W1_ATTRIBUTE = "w1";
+        private final static String BIGRAM_W2_ATTRIBUTE = "w2";
+        private final static String BIGRAM_FREQ_ATTRIBUTE = "p";
+
+        String mW1;
+        final HashMap<String, ArrayList<WeightedString>> mBigramsMap;
+
+        public BigramHandler() {
+            mW1 = null;
+            mBigramsMap = new HashMap<String, ArrayList<WeightedString>>();
+        }
+
+        @Override
+        public void startElement(String uri, String localName, String qName, Attributes attrs) {
+            if (BIGRAM_W1_TAG.equals(localName)) {
+                mW1 = attrs.getValue(uri, BIGRAM_W1_ATTRIBUTE);
+            } else if (BIGRAM_W2_TAG.equals(localName)) {
+                String w2 = attrs.getValue(uri, BIGRAM_W2_ATTRIBUTE);
+                int freq = Integer.parseInt(attrs.getValue(uri, BIGRAM_FREQ_ATTRIBUTE));
+                WeightedString bigram = new WeightedString(w2, freq / 8);
+                ArrayList<WeightedString> bigramList = mBigramsMap.get(mW1);
+                if (null == bigramList) bigramList = new ArrayList<WeightedString>();
+                bigramList.add(bigram);
+                mBigramsMap.put(mW1, bigramList);
+            }
+        }
+
+        public HashMap<String, ArrayList<WeightedString>> getBigramMap() {
+            return mBigramsMap;
+        }
+    }
+
+    /**
+     * Reads a dictionary from an XML file.
+     *
+     * This is the public method that will parse an XML file and return the corresponding memory
+     * representation.
+     *
+     * @param unigrams the file to read the data from.
+     * @return the in-memory representation of the dictionary.
+     */
+    public static FusionDictionary readDictionaryXml(InputStream unigrams, InputStream bigrams)
+            throws SAXException, IOException, ParserConfigurationException {
+        final SAXParserFactory factory = SAXParserFactory.newInstance();
+        factory.setNamespaceAware(true);
+        final SAXParser parser = factory.newSAXParser();
+        final BigramHandler bigramHandler = new BigramHandler();
+        if (null != bigrams) parser.parse(bigrams, bigramHandler);
+
+        final FusionDictionary dict = new FusionDictionary();
+        final UnigramHandler unigramHandler =
+                new UnigramHandler(dict, bigramHandler.getBigramMap());
+        parser.parse(unigrams, unigramHandler);
+        return dict;
+    }
+
+    /**
+     * Reads a dictionary in the first, legacy XML format
+     *
+     * This method reads data from the parser and creates a new FusionDictionary with it.
+     * The format parsed by this method is the format used before Ice Cream Sandwich,
+     * which has no support for bigrams or shortcuts.
+     * It is important to note that this method expects the parser to have already eaten
+     * the first, all-encompassing tag.
+     *
+     * @param xpp the parser to read the data from.
+     * @return the parsed dictionary.
+     */
+
+    /**
+     * Writes a dictionary to an XML file.
+     *
+     * The output format is the "second" format, which supports bigrams and shortcuts.
+     *
+     * @param destination a destination stream to write to.
+     * @param dict the dictionary to write.
+     */
+    public static void writeDictionaryXml(Writer destination, FusionDictionary dict)
+            throws IOException {
+        final TreeSet<Word> set = new TreeSet<Word>();
+        for (Word word : dict) {
+            set.add(word);
+        }
+        // TODO: use an XMLSerializer if this gets big
+        destination.write("<wordlist format=\"2\">\n");
+        for (Word word : set) {
+            destination.write("  <" + WORD_TAG + " " + WORD_ATTR + "=\"" + word.mWord + "\" "
+                    + FREQUENCY_ATTR + "=\"" + word.mFrequency + "\">");
+            if (null != word.mBigrams) {
+                destination.write("\n");
+                for (WeightedString bigram : word.mBigrams) {
+                    destination.write("    <" + BIGRAM_TAG + " " + FREQUENCY_ATTR + "=\""
+                            + bigram.mFrequency + "\">" + bigram.mWord + "</" + BIGRAM_TAG + ">\n");
+                }
+                destination.write("  ");
+            }
+            destination.write("</" + WORD_TAG + ">\n");
+        }
+        destination.write("</wordlist>\n");
+        destination.close();
+    }
+}
diff --git a/tools/makedict/src/com/android/tools/dict/BigramDictionary.java b/tools/makedict/src/com/android/tools/dict/BigramDictionary.java
deleted file mode 100644
index 35115bf..0000000
--- a/tools/makedict/src/com/android/tools/dict/BigramDictionary.java
+++ /dev/null
@@ -1,286 +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.tools.dict;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Helper for MakeBinaryDictionary
- * Deals with all the bigram data
- */
-public class BigramDictionary {
-
-    /*
-     * Must match the values in the client side which is located in dictionary.cpp & dictionary.h
-     * Changing these values will generate totally different structure which must be also reflected
-     * on the client side.
-     */
-    public static final int FLAG_BIGRAM_READ = 0x80;
-    public static final int FLAG_BIGRAM_CHILDEXIST = 0x40;
-    public static final int FLAG_BIGRAM_CONTINUED = 0x80;
-    public static final int FLAG_BIGRAM_FREQ = 0x7F;
-
-    public static final int FOR_REVERSE_LOOKUPALL = -99;
-
-    public ArrayList<String> mBigramToFill = new ArrayList<String>();
-    public ArrayList<Integer> mBigramToFillAddress = new ArrayList<Integer>();
-
-    public HashMap<String, Bigram> mBi;
-
-    public boolean mHasBigram;
-
-    public BigramDictionary(String bigramSrcFilename, boolean hasBigram) {
-        mHasBigram = hasBigram;
-        loadBigram(bigramSrcFilename);
-    }
-
-    private void loadBigram(String filename) {
-        mBi = new HashMap<String, Bigram>();
-        if (!mHasBigram) {
-            System.out.println("Number of bigrams = " + Bigram.sBigramNum);
-            return;
-        }
-        try {
-            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
-            parser.parse(new File(filename), new DefaultHandler() {
-                String w1 = null;
-                boolean inWord1 = false;
-                boolean inWord2 = false;
-                int freq = 0, counter = 0;
-                Bigram tempBigram = null;
-
-                @Override
-                public void startElement(String uri, String localName,
-                        String qName, Attributes attributes) {
-                    if (qName.equals("bi")) {
-                        inWord1 = true;
-                        w1 = attributes.getValue(0);
-                        int count = Integer.parseInt(attributes.getValue(1));
-                        tempBigram = new Bigram(count);
-                        counter = 0;
-                    } else if (qName.equals("w")) {
-                        inWord2 = true;
-                        String word2 = attributes.getValue(0);
-                        int freq = Integer.parseInt(attributes.getValue(1));
-                        tempBigram.setWord2(counter, word2, freq);
-                        counter++;
-                        Bigram.sBigramNum++;
-                    }
-                }
-
-                @Override
-                public void endElement(String uri, String localName,
-                        String qName) {
-                    if (inWord2) {
-                        inWord2 = false;
-                    } else if (inWord1) {
-                        inWord1 = false;
-                        mBi.put(w1, tempBigram);
-                    }
-                }
-            });
-        } catch (Exception ioe) {
-            System.err.println("Exception in parsing bigram\n" + ioe);
-            ioe.printStackTrace();
-        }
-        System.out.println("Number of bigrams = " + Bigram.sBigramNum);
-    }
-
-    byte[] writeBigrams(byte[] dict, Map<String, Integer> mDictionary) {
-        for (int i = 0; i < mBigramToFill.size(); i++) {
-            String w1 = mBigramToFill.get(i);
-            int address = mBigramToFillAddress.get(i);
-
-            Bigram temp = mBi.get(w1);
-            int word2Count = temp.count;
-            int j4;
-            for (int j = 0; j < word2Count; j++) {
-                if (!mDictionary.containsKey(temp.word2[j])) {
-                    System.out.println("Not in dictionary: " + temp.word2[j]);
-                    System.exit(0);
-                } else {
-                    j4 = (j * 4);
-                    int addressOfWord2 = mDictionary.get(temp.word2[j]);
-                    dict[address + j4 + 0] = (byte) (((addressOfWord2 & 0x3F0000) >> 16)
-                            | FLAG_BIGRAM_READ);
-                    dict[address + j4 + 1] = (byte) ((addressOfWord2 & 0x00FF00) >> 8);
-                    dict[address + j4 + 2] = (byte) ((addressOfWord2 & 0x0000FF));
-
-                    if (j == (word2Count - 1)) {
-                        dict[address + j4 + 3] = (byte) (temp.freq[j] & FLAG_BIGRAM_FREQ);
-                    } else {
-                        dict[address + j4 + 3] = (byte) ((temp.freq[j] & FLAG_BIGRAM_FREQ)
-                                | FLAG_BIGRAM_CONTINUED);
-                    }
-                }
-            }
-        }
-
-        return dict;
-    }
-
-    void reverseLookupAll(Map<String, Integer> mDictionary, byte[] dict) {
-        Set<String> st = mDictionary.keySet();
-        for (String s : st) {
-            searchForTerminalNode(mDictionary.get(s), FOR_REVERSE_LOOKUPALL, dict);
-        }
-    }
-
-    void searchForTerminalNode(int bigramAddress, int frequency, byte[] dict) {
-        StringBuilder sb = new StringBuilder(48);
-        int pos;
-        boolean found = false;
-        int followDownBranchAddress = 2;
-        char followingChar = ' ';
-        int depth = 0;
-        int totalLoopCount = 0;
-
-        while (!found) {
-            boolean followDownAddressSearchStop = false;
-            boolean firstAddress = true;
-            boolean haveToSearchAll = true;
-
-            if (depth > 0) {
-                sb.append(followingChar);
-            }
-            pos = followDownBranchAddress; // pos start at count
-            int count = dict[pos] & 0xFF;
-            pos++;
-            for (int i = 0; i < count; i++) {
-                totalLoopCount++;
-                // pos at data
-                pos++;
-                // pos now at flag
-                if (!MakeBinaryDictionary.getFirstBitOfByte(pos, dict)) { // non-terminal
-                    if (!followDownAddressSearchStop) {
-                        int addr = MakeBinaryDictionary.get22BitAddress(pos, dict);
-                        if (addr > bigramAddress) {
-                            followDownAddressSearchStop = true;
-                            if (firstAddress) {
-                                firstAddress = false;
-                                haveToSearchAll = true;
-                            } else if (!haveToSearchAll) {
-                                break;
-                            }
-                        } else {
-                            followDownBranchAddress = addr;
-                            followingChar = (char) (0xFF & dict[pos-1]);
-                            if(firstAddress) {
-                                firstAddress = false;
-                                haveToSearchAll = false;
-                            }
-                        }
-                    }
-                    pos += 3;
-                } else if (MakeBinaryDictionary.getFirstBitOfByte(pos, dict)) { // terminal
-                    // found !!
-                    if (bigramAddress == (pos-1)) {
-                        sb.append((char) (0xFF & dict[pos-1]));
-                        found = true;
-                        break;
-                    }
-
-                    // address + freq (4 byte)
-                    if (MakeBinaryDictionary.getSecondBitOfByte(pos, dict)) {
-                        if (!followDownAddressSearchStop) {
-                            int addr = MakeBinaryDictionary.get22BitAddress(pos, dict);
-                            if (addr > bigramAddress) {
-                                followDownAddressSearchStop = true;
-                                if (firstAddress) {
-                                    firstAddress = false;
-                                    haveToSearchAll = true;
-                                } else if (!haveToSearchAll) {
-                                    break;
-                                }
-                            } else {
-                                followDownBranchAddress = addr;
-                                followingChar = (char) (0xFF & dict[pos-1]);
-                                if(firstAddress) {
-                                    firstAddress = false;
-                                    haveToSearchAll = true;
-                                }
-                            }
-                        }
-                        pos += 4;
-                    } else { // freq only (2 byte)
-                        pos += 2;
-                    }
-                    // skipping bigram
-                    int bigramExist = (dict[pos] & FLAG_BIGRAM_READ);
-                    if (bigramExist > 0) {
-                        int nextBigramExist = 1;
-                        while (nextBigramExist > 0) {
-                            pos += 3;
-                            nextBigramExist = (dict[pos++] & FLAG_BIGRAM_CONTINUED);
-                        }
-                    } else {
-                        pos++;
-                    }
-                }
-            }
-            depth++;
-            if (followDownBranchAddress == 2) {
-                System.out.println("ERROR!!! Cannot find bigram!!");
-                System.exit(0);
-            }
-        }
-
-        if (frequency == FOR_REVERSE_LOOKUPALL) {
-            System.out.println("Reverse: " + sb.toString() + " (" + bigramAddress + ")"
-                    + "   Loop: " + totalLoopCount);
-        } else {
-            System.out.println("   bigram: " + sb.toString() + " (" + bigramAddress + ") freq: "
-                    + frequency + "   Loop: " + totalLoopCount);
-        }
-    }
-
-    static class Bigram {
-        String[] word2;
-        int[] freq;
-        int count;
-        static int sBigramNum = 0;
-
-        String getSecondWord(int i) {
-            return word2[i];
-        }
-
-        int getFrequency(int i) {
-            return (freq[i] == 0) ? 1 : freq[i];
-        }
-
-        void setWord2(int index, String word2, int freq) {
-            this.word2[index] = word2;
-            this.freq[index] = freq;
-        }
-
-        public Bigram(int word2Count) {
-            count = word2Count;
-            word2 = new String[word2Count];
-            freq = new int[word2Count];
-        }
-    }
-}
diff --git a/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java b/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java
deleted file mode 100644
index 4a285ff..0000000
--- a/tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java
+++ /dev/null
@@ -1,445 +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.
- */
-
-package com.android.tools.dict;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.helpers.DefaultHandler;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-
-/**
- * Compresses a list of words, frequencies, and bigram data
- * into a tree structured binary dictionary.
- * Dictionary Version: 200 (may contain bigrams)
- *  Version number started from 200 rather than 1 because we wanted to prevent number of roots in
- *  any old dictionaries being mistaken as the version number. There is not a chance that there
- *  will be more than 200 roots. Version number should be increased when there is structural change
- *  in the data. There is no need to increase the version when only the words in the data changes.
- */
-public class MakeBinaryDictionary {
-    private static final int VERSION_NUM = 200;
-
-    private static final String TAG_WORD = "w";
-    private static final String ATTR_FREQ = "f";
-
-    private static final int FLAG_ADDRESS_MASK  = 0x400000;
-    private static final int FLAG_TERMINAL_MASK = 0x800000;
-    private static final int ADDRESS_MASK = 0x3FFFFF;
-
-    private static final int INITIAL_STRING_BUILDER_CAPACITY = 48;
-
-    /**
-     * Unit for this variable is in bytes
-     * If destination file name is main.dict and file limit causes dictionary to be separated into
-     * multiple file, it will generate main0.dict, main1.dict, and so forth.
-     */
-    private static int sOutputFileSize;
-    private static boolean sSplitOutput;
-
-    private static final CharNode EMPTY_NODE = new CharNode();
-
-    private List<CharNode> mRoots;
-    private Map<String, Integer> mDictionary;
-    private int mWordCount;
-
-    private BigramDictionary mBigramDict;
-
-    private static class CharNode {
-        char data;
-        int freq;
-        boolean terminal;
-        List<CharNode> children;
-        static int sNodes;
-
-        public CharNode() {
-            sNodes++;
-        }
-    }
-
-    private static void usage() {
-        System.err.println("Usage: makedict -s <src_dict.xml> [-b <src_bigram.xml>] "
-                + "-d <dest.dict> [--size filesize]");
-        System.exit(-1);
-    }
-    
-    public static void main(String[] args) {
-        int checkSource = -1;
-        int checkBigram = -1;
-        int checkDest = -1;
-        int checkFileSize = -1;
-        for (int i = 0; i < args.length; i+=2) {
-            if (args[i].equals("-s")) checkSource = (i + 1);
-            if (args[i].equals("-b")) checkBigram = (i + 1);
-            if (args[i].equals("-d")) checkDest = (i + 1);
-            if (args[i].equals("--size")) checkFileSize = (i + 1);
-        }
-        if (checkFileSize >= 0) {
-            sSplitOutput = true;
-            sOutputFileSize = Integer.parseInt(args[checkFileSize]);
-        } else {
-            sSplitOutput = false;
-        }
-        if (checkDest >= 0 && !args[checkDest].endsWith(".dict")) {
-            System.err.println("Error: Dictionary output file extension should be \".dict\"");
-            usage();
-        } else if (checkSource >= 0 && checkBigram >= 0 && checkDest >= 0 &&
-                ((!sSplitOutput && args.length == 6) || (sSplitOutput && args.length == 8))) {
-            new MakeBinaryDictionary(args[checkSource], args[checkBigram], args[checkDest]);
-        } else if (checkSource >= 0 && checkDest >= 0 &&
-                ((!sSplitOutput && args.length == 4) || (sSplitOutput && args.length == 6))) {
-            new MakeBinaryDictionary(args[checkSource], null, args[checkDest]);
-        } else {
-            usage();
-        }
-    }
-
-    private MakeBinaryDictionary(String srcFilename, String bigramSrcFilename,
-            String destFilename) {
-        System.out.println("Generating dictionary version " + VERSION_NUM);
-        mBigramDict = new BigramDictionary(bigramSrcFilename, (bigramSrcFilename != null));
-        populateDictionary(srcFilename);
-        writeToDict(destFilename);
-
-        // Enable the code below to verify that the generated tree is traversable
-        // and bigram data is stored correctly.
-        if (false) {
-            mBigramDict.reverseLookupAll(mDictionary, mDict);
-            traverseDict(2, new char[32], 0);
-        }
-    }
-
-    private void populateDictionary(String filename) {
-        mRoots = new ArrayList<CharNode>();
-        mDictionary = new HashMap<String, Integer>();
-        try {
-            SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
-            parser.parse(new File(filename), new DefaultHandler() {
-                boolean inWord;
-                int freq;
-                StringBuilder wordBuilder = new StringBuilder(INITIAL_STRING_BUILDER_CAPACITY);
-
-                @Override
-                public void startElement(String uri, String localName,
-                        String qName, Attributes attributes) {
-                    if (qName.equals(TAG_WORD)) {
-                        inWord = true;
-                        freq = Integer.parseInt(attributes.getValue(ATTR_FREQ));
-                        wordBuilder.setLength(0);
-                    }
-                }
-
-                @Override
-                public void characters(char[] data, int offset, int length) {
-                    // Ignore other whitespace
-                    if (!inWord) return;
-                    wordBuilder.append(data, offset, length);
-                }
-
-                @Override
-                public void endElement(String uri, String localName,
-                        String qName) {
-                    if (qName.equals(TAG_WORD)) {
-                        if (wordBuilder.length() >= 1) {
-                            addWordTop(wordBuilder.toString(), freq);
-                            mWordCount++;
-                        }
-                        inWord = false;
-                    }
-                }
-            });
-        } catch (Exception ioe) {
-            System.err.println("Exception in parsing\n" + ioe);
-            ioe.printStackTrace();
-        }
-        System.out.println("Nodes = " + CharNode.sNodes);
-    }
-
-    private static int indexOf(List<CharNode> children, char c) {
-        if (children == null) {
-            return -1;
-        }
-        for (int i = 0; i < children.size(); i++) {
-            if (children.get(i).data == c) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private void addWordTop(String word, int freq) {
-        if (freq < 0) {
-            freq = 0;
-        } else if (freq > 255) {
-            freq = 255;
-        }
-        char firstChar = word.charAt(0);
-        int index = indexOf(mRoots, firstChar);
-        if (index == -1) {
-            CharNode newNode = new CharNode();
-            newNode.data = firstChar;
-            index = mRoots.size();
-            mRoots.add(newNode);
-        }
-        final CharNode node = mRoots.get(index);
-        if (word.length() > 1) {
-            addWordRec(node, word, 1, freq);
-        } else {
-            node.terminal = true;
-            node.freq = freq;
-        }
-    }
-
-    private void addWordRec(CharNode parent, String word, int charAt, int freq) {
-        CharNode child = null;
-        char data = word.charAt(charAt);
-        if (parent.children == null) {
-            parent.children = new ArrayList<CharNode>();
-        } else {
-            for (int i = 0; i < parent.children.size(); i++) {
-                CharNode node = parent.children.get(i);
-                if (node.data == data) {
-                    child = node;
-                    break;
-                }
-            }
-        }
-        if (child == null) {
-            child = new CharNode();
-            parent.children.add(child);
-        }
-        child.data = data;
-        if (word.length() > charAt + 1) {
-            addWordRec(child, word, charAt + 1, freq);
-        } else {
-            child.terminal = true;
-            child.freq = freq;
-        }
-    }
-
-    private byte[] mDict;
-    private int mDictSize;
-    private static final int CHAR_WIDTH = 8;
-    private static final int FLAGS_WIDTH = 1; // Terminal flag (word end)
-    private static final int ADDR_WIDTH = 23; // Offset to children
-    private static final int FREQ_WIDTH_BYTES = 1;
-    private static final int COUNT_WIDTH_BYTES = 1;
-
-    private void addCount(int count) {
-        mDict[mDictSize++] = (byte) (0xFF & count);
-    }
-
-    private void addNode(CharNode node, String word1) {
-        if (node.terminal) { // store address of each word1 for bigram dic generation
-            mDictionary.put(word1, mDictSize);
-        }
-
-        int charData = 0xFFFF & node.data;
-        if (charData > 254) {
-            mDict[mDictSize++] = (byte) 255;
-            mDict[mDictSize++] = (byte) ((node.data >> 8) & 0xFF);
-            mDict[mDictSize++] = (byte) (node.data & 0xFF);
-        } else {
-            mDict[mDictSize++] = (byte) (0xFF & node.data);
-        }
-        if (node.children != null) {
-            mDictSize += 3; // Space for children address
-        } else {
-            mDictSize += 1; // Space for just the terminal/address flags
-        }
-        if ((0xFFFFFF & node.freq) > 255) {
-            node.freq = 255;
-        }
-        if (node.terminal) {
-            byte freq = (byte) (0xFF & node.freq);
-            mDict[mDictSize++] = freq;
-            // bigram
-            if (mBigramDict.mBi.containsKey(word1)) {
-                int count = mBigramDict.mBi.get(word1).count;
-                mBigramDict.mBigramToFill.add(word1);
-                mBigramDict.mBigramToFillAddress.add(mDictSize);
-                mDictSize += (4 * count);
-            } else {
-                mDict[mDictSize++] = (byte) (0x00);
-            }
-        }
-    }
-
-    private int mNullChildrenCount = 0;
-    private int mNotTerminalCount = 0;
-
-    private void updateNodeAddress(int nodeAddress, CharNode node,
-            int childrenAddress) {
-        if ((mDict[nodeAddress] & 0xFF) == 0xFF) { // 3 byte character
-            nodeAddress += 2;
-        }
-        childrenAddress = ADDRESS_MASK & childrenAddress;
-        if (childrenAddress == 0) {
-            mNullChildrenCount++;
-        } else {
-            childrenAddress |= FLAG_ADDRESS_MASK;
-        }
-        if (node.terminal) {
-            childrenAddress |= FLAG_TERMINAL_MASK;
-        } else {
-            mNotTerminalCount++;
-        }
-        mDict[nodeAddress + 1] = (byte) (childrenAddress >> 16);
-        if ((childrenAddress & FLAG_ADDRESS_MASK) != 0) {
-            mDict[nodeAddress + 2] = (byte) ((childrenAddress & 0xFF00) >> 8);
-            mDict[nodeAddress + 3] = (byte) ((childrenAddress & 0xFF));
-        }
-    }
-
-    private void writeWordsRec(List<CharNode> children, StringBuilder word) {
-        if (children == null || children.size() == 0) {
-            return;
-        }
-        final int childCount = children.size();
-        addCount(childCount);
-        int[] childrenAddresses = new int[childCount];
-        for (int j = 0; j < childCount; j++) {
-            CharNode child = children.get(j);
-            childrenAddresses[j] = mDictSize;
-            word.append(child.data);
-            addNode(child, word.toString());
-            word.setLength(word.length() - 1);
-        }
-        for (int j = 0; j < childCount; j++) {
-            CharNode child = children.get(j);
-            int nodeAddress = childrenAddresses[j];
-            int cacheDictSize = mDictSize;
-            word.append(child.data);
-            writeWordsRec(child.children, word);
-            word.setLength(word.length() - 1);
-            updateNodeAddress(nodeAddress, child, child.children != null ? cacheDictSize : 0);
-        }
-    }
-
-    private void writeToDict(String dictFilename) {
-        // 4MB max, 22-bit offsets
-        mDict = new byte[4 * 1024 * 1024]; // 4MB upper limit. Actual is probably
-                                           // < 1MB in most cases, as there is a limit in the
-                                           // resource size in apks.
-        mDictSize = 0;
-
-        mDict[mDictSize++] = (byte) (0xFF & VERSION_NUM); // version info
-        mDict[mDictSize++] = (byte) (0xFF & (mBigramDict.mHasBigram ? 1 : 0));
-
-        final StringBuilder word = new StringBuilder(INITIAL_STRING_BUILDER_CAPACITY);
-        writeWordsRec(mRoots, word);
-        mDict = mBigramDict.writeBigrams(mDict, mDictionary);
-        System.out.println("Dict Size = " + mDictSize);
-        if (!sSplitOutput) {
-            sOutputFileSize = mDictSize;
-        }
-        try {
-            int currentLoc = 0;
-            int i = 0;
-            int extension = dictFilename.indexOf(".dict");
-            String filename = dictFilename.substring(0, extension);
-            while (mDictSize > 0) {
-                FileOutputStream fos;
-                if (sSplitOutput) {
-                    fos = new FileOutputStream(filename + i + ".dict");
-                } else {
-                    fos = new FileOutputStream(filename + ".dict");
-                }
-                if (mDictSize > sOutputFileSize) {
-                    fos.write(mDict, currentLoc, sOutputFileSize);
-                    mDictSize -= sOutputFileSize;
-                    currentLoc += sOutputFileSize;
-                } else {
-                    fos.write(mDict, currentLoc, mDictSize);
-                    mDictSize = 0;
-                }
-                fos.close();
-                i++;
-            }
-        } catch (IOException ioe) {
-            System.err.println("Error writing dict file:" + ioe);
-        }
-    }
-
-    private void traverseDict(int pos, char[] word, int depth) {
-        int count = mDict[pos++] & 0xFF;
-        for (int i = 0; i < count; i++) {
-            char c = (char) (mDict[pos++] & 0xFF);
-            if (c == 0xFF) { // two byte character
-                c = (char) (((mDict[pos] & 0xFF) << 8) | (mDict[pos+1] & 0xFF));
-                pos += 2;
-            }
-            word[depth] = c;
-            boolean terminal = getFirstBitOfByte(pos, mDict);
-            int address = 0;
-            if ((mDict[pos] & (FLAG_ADDRESS_MASK >> 16)) > 0) { // address check
-                address = get22BitAddress(pos, mDict);
-                pos += 3;
-            } else {
-                pos += 1;
-            }
-            if (terminal) {
-                showWord(word, depth + 1, mDict[pos] & 0xFF);
-                pos++;
-
-                int bigramExist = (mDict[pos] & mBigramDict.FLAG_BIGRAM_READ);
-                if (bigramExist > 0) {
-                    int nextBigramExist = 1;
-                    while (nextBigramExist > 0) {
-                        int bigramAddress = get22BitAddress(pos, mDict);
-                        pos += 3;
-                        int frequency = (mBigramDict.FLAG_BIGRAM_FREQ & mDict[pos]);
-                        mBigramDict.searchForTerminalNode(bigramAddress, frequency, mDict);
-                        nextBigramExist = (mDict[pos++] & mBigramDict.FLAG_BIGRAM_CONTINUED);
-                    }
-                } else {
-                    pos++;
-                }
-            }
-            if (address != 0) {
-                traverseDict(address, word, depth + 1);
-            }
-        }
-    }
-
-    private static void showWord(char[] word, int size, int freq) {
-        System.out.print(new String(word, 0, size) + " " + freq + "\n");
-    }
-
-    /* package */ static int get22BitAddress(int pos, byte[] dict) {
-        return ((dict[pos + 0] & 0x3F) << 16)
-                | ((dict[pos + 1] & 0xFF) << 8)
-                | ((dict[pos + 2] & 0xFF));
-    }
-
-    /* package */ static boolean getFirstBitOfByte(int pos, byte[] dict) {
-        return (dict[pos] & 0x80) > 0;
-    }
-
-    /* package */ static boolean getSecondBitOfByte(int pos, byte[] dict) {
-        return (dict[pos] & 0x40) > 0;
-    }
-}
diff --git a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
new file mode 100644
index 0000000..79cf14b
--- /dev/null
+++ b/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.android.inputmethod.latin.FusionDictionary.Node;
+
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for BinaryDictInputOutput.
+ */
+public class BinaryDictInputOutputTest extends TestCase {
+
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    // Test the flattened array contains the expected number of nodes, and
+    // that it does not contain any duplicates.
+    public void testFlattenNodes() {
+        final FusionDictionary dict = new FusionDictionary();
+        dict.add("foo", 1, null);
+        dict.add("fta", 1, null);
+        dict.add("ftb", 1, null);
+        dict.add("bar", 1, null);
+        dict.add("fool", 1, null);
+        final ArrayList<Node> result = BinaryDictInputOutput.flattenTree(dict.mRoot);
+        assertEquals(4, result.size());
+        while (!result.isEmpty()) {
+            final Node n = result.remove(0);
+            assertFalse("Flattened array contained the same node twice", result.contains(n));
+        }
+    }
+
+}