diff options
98 files changed, 3425 insertions, 1249 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/method.xml b/java/res/xml/method.xml index fbbc7fbce..0bf560d5a 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -20,10 +20,8 @@ <!-- The attributes in this XML file provide configuration information --> <!-- for the Input Method Manager. --> -<!-- Keyboard: en_US, en_GB, ar, cs, da, de, es, es_US, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, nb, nl, pl, pt, ru, sr, sv, tr --> -<!-- Voice: af, cs, da, de, en, es, fr, it, ja, ko, nl, pl, pt, ru, tr, yue, zh, zu --> +<!-- Keyboard: en_US, en_GB, ar, cs, da, de, de_ZZ, es, es_US, fi, fr, fr_CA, fr_CH, hr, hu, it, iw, nb, nl, pl, pt, ru, sr, sv, tr --> <!-- TODO: use <lang>_keyboard icon instead of a common keyboard icon. --> -<!-- TODO: use <lang>_mic icon instead of a common mic icon. --> <!-- If IME doesn't have an applicable subtype, the first subtype will be used as a default subtype.--> <input-method xmlns:android="http://schemas.android.com/apk/res/android" 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/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 2df2994f6..bc021a690 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -83,6 +83,11 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { // HORIZONTAL ELLIPSIS "...", character for popup hint. private static final String POPUP_HINT_CHAR = "\u2026"; + // Margin between the label and the icon on a key that has both of them. + // Specified by the fraction of the key width. + // TODO: Use resource parameter for this value. + private static final float LABEL_ICON_MARGIN = 0.05f; + // Main keyboard private Keyboard mKeyboard; private final KeyDrawParams mKeyDrawParams; @@ -538,11 +543,13 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { positionX = centerX - labelCharWidth * 7 / 4; paint.setTextAlign(Align.LEFT); } else if (key.hasLabelWithIconLeft() && icon != null) { - labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth(); + labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth() + + (int)(LABEL_ICON_MARGIN * keyWidth); positionX = centerX + labelWidth / 2; paint.setTextAlign(Align.RIGHT); } else if (key.hasLabelWithIconRight() && icon != null) { - labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth(); + labelWidth = getLabelWidth(label, paint) + icon.getIntrinsicWidth() + + (int)(LABEL_ICON_MARGIN * keyWidth); positionX = centerX - labelWidth / 2; paint.setTextAlign(Align.LEFT); } else { @@ -734,7 +741,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { canvas.translate(-x, -y); } - private static void drawHorizontalLine(Canvas canvas, float y, float w, int color, Paint paint) { + private static void drawHorizontalLine(Canvas canvas, float y, float w, int color, + Paint paint) { paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(1.0f); paint.setColor(color); 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/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 44e999572..d2b6bcdf2 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -126,7 +126,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService { // Note : this must be reentrant /** * Gets a list of suggestions for a specific string. This returns a list of possible - * corrections for the text passed as an arguments. It may split or group words, and + * corrections for the text passed as an argument. It may split or group words, and * even perform grammatical analysis. */ @Override @@ -153,9 +153,14 @@ public class AndroidSpellCheckerService extends SpellCheckerService { composer.add(character, proximities, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE); } - dictionary.getWords(composer, suggestionsGatherer, mProximityInfo); - final boolean isInDict = dictionary.isValidWord(text); - final String[] suggestions = suggestionsGatherer.getGatheredSuggestions(); + final boolean isInDict; + final String[] suggestions; + synchronized(dictionary) { + // TODO: make the dictionary reentrant so that we don't have to synchronize here + dictionary.getWords(composer, suggestionsGatherer, mProximityInfo); + isInDict = dictionary.isValidWord(text); + suggestions = suggestionsGatherer.getGatheredSuggestions(); + } final int flags = (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0) 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/tests/res/raw/testtext.txt b/tests/res/raw/testtext.txt deleted file mode 100644 index eca20c05f..000000000 --- a/tests/res/raw/testtext.txt +++ /dev/null @@ -1,24 +0,0 @@ -This text is used as test text for measuring performance of dictionary prediction. Any text can be put into this file to test the performance (total keystroke savings). -When you think about “information,” what probably comes to mind are streams of words and numbers. Google’s pretty good at organizing these types of information, but consider all the things you can’t express with words: what does it look like in the middle of a sandstorm? What are some great examples of Art Nouveau architecture? Should I consider wedding cupcakes instead of a traditional cake? -This is why we built Google Images in 2001. We realized that for many searches, the best answer wasn’t text—it was an image or a set of images. The service has grown quite a bit since then. In 2001, we indexed around 250 million images. By 2005, we had indexed over 1 billion. And today, we have an index of over 10 billion images. -It’s not just about quantity, though. Over the past decade we’ve been baking deep computer science into Google Images to make it even faster and easier for you to find precisely the right images. We not only find images for pretty much anything you type in; we can also instantly pull out images of clip art, line drawings, faces and even colors. -There’s even more sophisticated computer vision technology powering our “Similar images” tool. For example, did you know there are nine subspecies of leopards, each with a distinct pattern of spots? Google Images can recognize the difference, returning just leopards of a particular subspecies. It can tell you the name of the subspecies in a particular image—even if that image isn’t labeled—because other similar leopard images on the web are labeled with that subspecies’s name. -And our “Similar colors” refinement doesn’t just return images based on the overall color of an image. If it did, lots of images would simply be classified as “white.” If you’re looking for [tulips] and you refine results to “white,” you really want images in which the tulips themselves are white—not the surrounding image. It takes some heavy-duty algorithmic wizardry and processing power for a search engine to understand what the items of interest are in all the images out there. -Those are just a few of the technologies we’ve built to make Google Images more useful. Meanwhile, the quantity and variety of images on the web has ballooned since 2001, and images have become one of the most popular types of content people search for. So over the next few days we’re rolling out an update to Google Images to match the scope and beauty of this fast-growing visual web, and to bring to the surface some of the powerful technology behind Images. -Here’s what’s new in this refreshed design of Google Images: -Dense tiled layout designed to make it easy to look at lots of images at once. We want to get the app out of the way so you can find what you’re really looking for. -Instant scrolling between pages, without letting you get lost in the images. You can now get up to 1,000 images, all in one scrolling page. And we’ll show small, unobtrusive page numbers so you don’t lose track of where you are. -Larger thumbnail previews on the results page, designed for modern browsers and high-res screens. -A hover pane that appears when you mouse over a given thumbnail image, giving you a larger preview, more info about the image and other image-specific features such as “Similar images.” -Once you click on an image, you’re taken to a new landing page that displays a large image in context, with the website it’s hosted on visible right behind it. Click anywhere outside the image, and you’re right in the original page where you can learn more about the source and context. -Optimized keyboard navigation for faster scrolling through many pages, taking advantage of standard web keyboard shortcuts such as Page Up / Page Down. It’s all about getting you to the info you need quickly, so you can get on with actually building that treehouse or buying those flowers. -Apple's not really ready to say it's sorry about the iPhone 4 antenna design, but it is willing to give all you darn squeaky wheels free cases for your trouble. Since Apple can't build its own Bumpers fast enough, it will give you a few options and let you decide, then send it your way for free as long as you purchased the phone before September 30th. Not good enough for you? Well, if you already bought a bumper from Apple you'll get a refund, and you can also return your phone for a full refund within 30 days as long as it's unharmed. -This solution comes at the end of 22 days of Apple engineers "working their butts off," according to Steve, with "physics" ultimately being pinned as the main culprit. Apple claims you can replicate the left-handed "death grip" bar-dropping problem on the BlackBerry Bold 9700, HTC Droid Eris, and Samsung Omnia II, and that "phones aren't perfect." Steve also claims that only 0.55% of people who bought the iPhone 4 have called into AppleCare to complain about the antenna, and the phone has a 1.7% return rate at AT&T, compared to 6% with the 3GS, though he would cop to a slight increase in dropped calls over the iPhone 3GS. For this Steve has what he confesses to be a pet theory: that 3GS users were using the case they had from the 3G, and therefore weren't met with the horrible reality of a naked, call dropping handset. Hence the free case solution, which will probably satisfy some, infuriate others, and never even blip onto the radar of many of the massive horde of consumers that's devoured this product in unprecedented numbers. -Update: Our own Richard Lai just waltzed down to the Regent Street Apple Store in London with his iPhone Bumper receipt in hand. A few minutes later he left with cold, hard cash, and kept the Bumper to boot. Seems as if the refund effort is a go, at least over in the UK. -Update 2: We've heard from several tipsters saying Apple no longer does Bumper refunds at its stores; customers will now have to make an online claim instead. Looks like we got super lucky. -If you have ever received an instant message, text message, or any text-based chat message that seemed to be written in a foreign language, this Webopedia Quick Reference will help you decipher the text chat lingo by providing the definitions to more than 1,300 chat, text message, and Twitter abbreviations. -With the popularity and rise in real-time text-based communications, such as Facebook, Twitter, instant messaging, e-mail, Internet and online gaming services, chat rooms, discussion boards and mobile phone text messaging (SMS), came the emergence of a new language tailored to the immediacy and compactness of these new communication media. -While it does seem incredible that there are so many chat abbreviations, remember that different chat abbreviations are used by different groups of people when communicating online. Some of the following chat abbreviations may be familiar to you, while others may be foreign because they are used by a group of people with different online interests and hobbies than your own. For example, people playing online games are likely to use chat abbreviations that are different than those used by someone running a financial blog updating their Twitter status. -Twitter is a free microblog, or social messaging tool that lets people stay connected through brief text message updates up to 140 characters in length. Twitter is based on you answering the question "What are you doing?" You then post thoughts, observations and goings-on during the day in answer to that question. Your update is posted on your Twitter profile page through SMS text messaging, the Twitter Web site, instant messaging, RSS, e-mail or through other social applications and sites, such as Facebook. -As with any new social medium, there is an entire vocabulary that users of the Twitter service adopt. Many of the new lingo Twitter-based terms and phrases are used to describe the collection of people who use the service, while other terms are used in reference to describe specific functions and features of the service itself. Also, there are a number of "chat terms," which are basically shorthand abbreviations that users often include in their tweets. Lastly, our guide also provides descriptions to a number of Twitter tools and applications that you can use to enhance your Twitter experience. -Here are definitions to more than 100 Twitter-related abbreviations, words, phrases, and tools that are associated with the Twitter microblogging service. If you know of a Twitter slang term or application name that is not included in our Twitter Dictionary, please let us know. diff --git a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java b/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java deleted file mode 100644 index 7af566b03..000000000 --- a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2010,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.tests.R; - -import android.content.res.AssetFileDescriptor; -import android.content.res.Configuration; -import android.text.TextUtils; -import android.util.Slog; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.Locale; -import java.util.StringTokenizer; - -public class SuggestPerformanceTests extends SuggestTestsBase { - private static final String TAG = SuggestPerformanceTests.class.getSimpleName(); - - private String mTestText; - private SuggestHelper mHelper; - - @Override - protected void setUp() throws Exception { - super.setUp(); - final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test); - mHelper = new SuggestHelper( - getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(), - createKeyboardId(Locale.US, Configuration.ORIENTATION_PORTRAIT)); - loadString(R.raw.testtext); - } - - private void loadString(int testFileId) { - final String testFile = getTestContext().getResources().getResourceName(testFileId); - BufferedReader reader = null; - try { - reader = new BufferedReader( - new InputStreamReader(openTestRawResource(testFileId))); - final StringBuilder sb = new StringBuilder(); - String line; - Slog.i(TAG, "Reading test file " + testFile); - while ((line = reader.readLine()) != null) { - sb.append(line); - sb.append(" "); - } - mTestText = sb.toString(); - } catch (Exception e) { - Slog.e(TAG, "Can not read " + testFile); - e.printStackTrace(); - } finally { - if (reader != null) { - try { - reader.close(); - } catch (Exception e) { - Slog.e(TAG, "Closing " + testFile + " failed"); - } - } - } - } - - /************************** Helper functions ************************/ - private int lookForBigramSuggestion(String prevWord, String currentWord) { - for (int i = 1; i < currentWord.length(); i++) { - final CharSequence prefix = currentWord.substring(0, i); - final CharSequence word = (i == 1) - ? mHelper.getBigramFirstSuggestion(prevWord, prefix) - : mHelper.getBigramAutoCorrection(prevWord, prefix); - if (TextUtils.equals(word, currentWord)) - return i; - } - return currentWord.length(); - } - - private double runText(boolean withBigrams) { - mHelper.setCorrectionMode( - withBigrams ? Suggest.CORRECTION_FULL_BIGRAM : Suggest.CORRECTION_FULL); - StringTokenizer st = new StringTokenizer(mTestText); - String prevWord = null; - int typeCount = 0; - int characterCount = 0; // without space - int wordCount = 0; - while (st.hasMoreTokens()) { - String currentWord = st.nextToken(); - boolean endCheck = false; - if (currentWord.matches("[\\w]*[\\.|?|!|*|@|&|/|:|;]")) { - currentWord = currentWord.substring(0, currentWord.length() - 1); - endCheck = true; - } - if (withBigrams && prevWord != null) { - typeCount += lookForBigramSuggestion(prevWord, currentWord); - } else { - typeCount += lookForBigramSuggestion(null, currentWord); - } - characterCount += currentWord.length(); - if (!endCheck) prevWord = currentWord; - wordCount++; - } - - double result = (double) (characterCount - typeCount) / characterCount * 100; - if (withBigrams) { - Slog.i(TAG, "with bigrams -> " + result + " % saved!"); - } else { - Slog.i(TAG, "without bigrams -> " + result + " % saved!"); - } - Slog.i(TAG, "\ttotal number of words: " + wordCount); - Slog.i(TAG, "\ttotal number of characters: " + mTestText.length()); - Slog.i(TAG, "\ttotal number of characters without space: " + characterCount); - Slog.i(TAG, "\ttotal number of characters typed: " + typeCount); - return result; - } - - - /************************** Performance Tests ************************/ - /** - * Compare the Suggest with and without bigram - * Check the log for detail - */ - public void testSuggestPerformance() { - assertTrue(runText(false) <= runText(true)); - } -} 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)); + } + } + +} |