aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/AndroidManifest.xml8
-rw-r--r--java/proguard.flags4
-rw-r--r--java/res/values-af/strings.xml9
-rw-r--r--java/res/values-am/strings.xml9
-rw-r--r--java/res/values-ar/strings.xml7
-rw-r--r--java/res/values-bg/strings.xml7
-rw-r--r--java/res/values-ca/strings.xml12
-rw-r--r--java/res/values-cs/strings.xml14
-rw-r--r--java/res/values-da/strings.xml7
-rw-r--r--java/res/values-de/strings.xml6
-rw-r--r--java/res/values-el/strings.xml10
-rw-r--r--java/res/values-en-rGB/strings.xml10
-rw-r--r--java/res/values-es-rUS/strings.xml10
-rw-r--r--java/res/values-es/strings.xml9
-rw-r--r--java/res/values-fa/strings.xml9
-rw-r--r--java/res/values-fi/strings.xml6
-rw-r--r--java/res/values-fr/strings.xml7
-rw-r--r--java/res/values-hr/strings.xml11
-rw-r--r--java/res/values-hu/strings.xml14
-rw-r--r--java/res/values-in/strings.xml12
-rw-r--r--java/res/values-it/strings.xml9
-rw-r--r--java/res/values-iw/strings.xml10
-rw-r--r--java/res/values-ja/strings.xml9
-rw-r--r--java/res/values-ko/strings.xml10
-rw-r--r--java/res/values-lt/strings.xml11
-rw-r--r--java/res/values-lv/strings.xml7
-rw-r--r--java/res/values-ms/strings.xml9
-rw-r--r--java/res/values-nb/strings.xml7
-rw-r--r--java/res/values-nl/strings.xml10
-rw-r--r--java/res/values-pl/strings.xml10
-rw-r--r--java/res/values-pt-rPT/strings.xml6
-rw-r--r--java/res/values-pt/strings.xml12
-rw-r--r--java/res/values-rm/strings.xml6
-rw-r--r--java/res/values-ro/strings.xml7
-rw-r--r--java/res/values-ru/strings.xml16
-rw-r--r--java/res/values-sk/strings.xml10
-rw-r--r--java/res/values-sl/strings.xml11
-rw-r--r--java/res/values-sr/strings.xml7
-rw-r--r--java/res/values-sv/strings.xml7
-rw-r--r--java/res/values-sw/strings.xml9
-rw-r--r--java/res/values-sw600dp/config.xml3
-rw-r--r--java/res/values-sw768dp/config.xml1
-rw-r--r--java/res/values-th/strings.xml10
-rw-r--r--java/res/values-tl/strings.xml14
-rw-r--r--java/res/values-tr/strings.xml9
-rw-r--r--java/res/values-uk/strings.xml9
-rw-r--r--java/res/values-vi/strings.xml10
-rw-r--r--java/res/values-zh-rCN/strings.xml16
-rw-r--r--java/res/values-zh-rTW/strings.xml7
-rw-r--r--java/res/values-zu/strings.xml9
-rw-r--r--java/res/values/attrs.xml1
-rw-r--r--java/res/values/config.xml5
-rw-r--r--java/res/values/donottranslate.xml4
-rw-r--r--java/res/values/strings.xml9
-rw-r--r--java/res/values/styles.xml2
-rw-r--r--java/res/xml-sw600dp/kbd_rows_russian.xml3
-rw-r--r--java/res/xml/kbd_rows_russian.xml1
-rw-r--r--java/res/xml/prefs.xml8
-rw-r--r--java/res/xml/spell_checker_settings.xml26
-rw-r--r--java/src/com/android/inputmethod/deprecated/recorrection/Recorrection.java5
-rw-r--r--java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java15
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/KeyboardShiftState.java2
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java89
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java156
-rw-r--r--java/src/com/android/inputmethod/latin/CandidateView.java6
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java24
-rw-r--r--java/src/com/android/inputmethod/latin/SubtypeSwitcher.java2
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsActivity.java43
-rw-r--r--java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java41
-rw-r--r--native/src/correction.cpp274
-rw-r--r--native/src/correction.h69
-rw-r--r--native/src/correction_state.h57
-rw-r--r--native/src/defines.h17
-rw-r--r--native/src/proximity_info.cpp4
-rw-r--r--native/src/proximity_info.h5
-rw-r--r--native/src/unigram_dictionary.cpp47
-rw-r--r--native/src/unigram_dictionary.h11
-rw-r--r--tools/makedict/Android.mk5
-rw-r--r--tools/makedict/etc/Android.mk5
-rwxr-xr-xtools/makedict/etc/makedict4
-rw-r--r--tools/makedict/etc/manifest.txt2
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/BinaryDictInputOutput.java1024
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/CharGroupInfo.java45
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/DictionaryMaker.java256
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/FusionDictionary.java602
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/MakedictLog.java40
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/PendingAttribute.java32
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/UnsupportedFormatException.java26
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/Word.java65
-rw-r--r--tools/makedict/src/com/android/inputmethod/latin/XmlDictInputOutput.java215
-rw-r--r--tools/makedict/src/com/android/tools/dict/BigramDictionary.java286
-rw-r--r--tools/makedict/src/com/android/tools/dict/MakeBinaryDictionary.java445
-rw-r--r--tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java55
93 files changed, 3404 insertions, 1081 deletions
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index d680f1894..548da1bbb 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 395950033..33af890ec 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 cbe64062f..8d3632826 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 4d8ace549..16595d22a 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 433081306..46f0c1d77 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 cc958af77..f2514ecce 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 2c9fbfbb6..6daca172e 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 38417a0d5..9cd237edd 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 97dc8a0ad..85cbb85d1 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 fdce3bb01..7a20f69c6 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 2629c2418..e0c503341 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 7a0449c27..f89c29304 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 4eacee950..87a15a145 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 aae38ca8d..c3b97c78c 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 bacc180a1..5a085d878 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 1b45be745..88a8ea093 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 e658da024..c35c9bb17 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 55d4b145c..4d27d2c9a 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 d682b82ec..afae17d91 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 d8bb604d4..f3506863f 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 d122f5087..f67c3e474 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 b49091335..31632e9b0 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 8ac407a06..08b572203 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 c9000d18a..83cf906c8 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 4113ef32c..24073f766 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 e5da1dc4e..2ec111a35 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 63fd65596..eef6ef2e2 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 e86582289..62140d3e8 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 ebb1c062e..26e138d55 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 1cf14166f..72687b58d 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 22c99f22b..7d1b6d0fa 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 167ae0e0c..793c78fd4 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 359dc7a12..9901b9f66 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 c317cfd1d..1dd083e55 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 68d1ee28e..64c2aa60b 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 a8c57008f..3e7ed1475 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 19bf04c8a..84498ad19 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 46daea90e..d465aaedc 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 2c8bc7318..61a7f1362 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 a916535b0..9c677d177 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 ade376a33..d539e0d82 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 091d62e59..0f8f106b7 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 780ff024a..c53ed2e65 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 b0806bed3..447baef79 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 103f01618..91db66e3c 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 cca56b842..3e55f469e 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 e7c684d2d..caf9d579e 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 189c17420..cdbd64dd1 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 aa65cdd82..fcf5118b7 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 4929c12ec..b4688fd93 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 172ca2f25..c2200b5ad 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 731f63f34..6327ede38 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 fb28766e7..f55e9bf53 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 ed69fc9f0..247bdba41 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 1ebd2cee1..a47eeed12 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 7588f6ccb..2f4b95e60 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 216d74946..3aeb52bcc 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 552e3cf4f..6d2218d2f 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 000000000..f402555c9
--- /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 d40728d25..bf2512d7b 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 @@ public class Recorrection implements SharedPreferences.OnSharedPreferenceChangeL
// 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 811470c26..21477a992 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -280,7 +280,6 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
}
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 @@ public class KeyboardSwitcher implements SharedPreferences.OnSharedPreferenceCha
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 0cde4e5b5..fd98456a8 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 android.util.Log;
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 2d50a6f46..3da670e2e 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.net.Uri;
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 @@ public class BinaryDictionaryFileDumper {
}
/**
- * 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 @@ public class BinaryDictionaryFileDumper {
}
/**
- * 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 @@ public class BinaryDictionaryFileDumper {
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 4b1c05adf..360cf21ca 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 @@ class BinaryDictionaryGetter {
*/
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 @@ class BinaryDictionaryGetter {
}
/**
+ * 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 @@ class BinaryDictionaryGetter {
* - 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 d779c8565..0640fd0b1 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -278,6 +278,7 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
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 @@ public class CandidateView extends LinearLayout implements OnClickListener, OnLo
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 class CandidateView extends LinearLayout implements OnClickListener, OnLo
}
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 c28e40d95..a932f03ac 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -190,8 +190,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
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 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
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 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
};
+ // 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 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
}
}
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 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar
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 f10b1b845..0a391a77e 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -318,7 +318,7 @@ public class SubtypeSwitcher {
// 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 000000000..483679a55
--- /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 000000000..9b821be35
--- /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 6d682c0c9..a4090a966 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 @@ void Correction::initCorrection(const ProximityInfo *pi, const int inputLength,
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 @@ int Correction::getFinalFreq(const int freq, unsigned short **word, int *wordLen
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;
-}
-
-void Correction::getProcessState(int *matchedCount, int *inputIndex, int *outputIndex,
- bool *traverseAllNodes, int *diffs) {
- *matchedCount = mMatchedCharCount;
- *inputIndex = mInputIndex;
- *outputIndex = mOutputIndex;
- *traverseAllNodes = mTraverseAllNodes;
- *diffs = mDiffs;
+ --(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::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 @@ int Correction::getInputIndex() {
}
// TODO: remove
-bool Correction::needsToTraverseAll() {
- return mTraverseAllNodes;
+bool Correction::needsToTraverseAllNodes() {
+ return mNeedsToTraverseAllNodes;
}
void Correction::incrementInputIndex() {
@@ -133,21 +147,32 @@ void Correction::incrementInputIndex() {
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 @@ Correction::CorrectionType Correction::processCharAndCalcState(
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 @@ Correction::CorrectionType Correction::processCharAndCalcState(
}
}
+ // 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 @@ Correction::CorrectionType Correction::processCharAndCalcState(
}
// 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 @@ inline static void multiplyRate(const int rate, int *freq) {
}
}
+/* 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 @@ int Correction::RankingAlgorithm::calculateFinalFreq(
}
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 @@ int Correction::RankingAlgorithm::calculateFinalFreq(
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 ae6c7a421..9d385a44e 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 @@ public:
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 @@ public:
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 000000000..267deda9b
--- /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 5a5d3ee0c..c1d08e695 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -94,20 +94,36 @@ static void prof_out(void) {
#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 @@ static void prof_out(void) {
#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 d437e251a..361bdacbf 100644
--- a/native/src/proximity_info.cpp
+++ b/native/src/proximity_info.cpp
@@ -68,6 +68,10 @@ bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
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 5034c3b89..75fc8fb63 100644
--- a/native/src/proximity_info.h
+++ b/native/src/proximity_info.h
@@ -46,6 +46,10 @@ public:
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 @@ private:
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 df1a2e273..6bc350505 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -187,20 +187,15 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo,
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 @@ void UnigramDictionary::getSuggestionCandidates(const int skipPos,
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 8bcd7cea5..cfe63ff79 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 @@ private:
// 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 b9fc5533d..6832b1cb6 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 @@ LOCAL_PATH := $(call my-dir)
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 da162868a..96a90cbe0 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 8420d6e5e..7c1c02e85 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 @@ fi
# 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 aa3a3e84c..948609da6 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 000000000..92f402d3e
--- /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 000000000..6badfd13a
--- /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 000000000..1ba01075e
--- /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 000000000..031f35dfe
--- /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 000000000..badb2ff3d
--- /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 000000000..e50202184
--- /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 000000000..511ad5675
--- /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 000000000..916165a41
--- /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 000000000..096bfd182
--- /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 35115bf2c..000000000
--- 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 4a285ff07..000000000
--- 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 000000000..79cf14b2b
--- /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));
+ }
+ }
+
+}