am 4bad47f9: (-s ours) Import revised translations. DO NOT MERGE
* commit '4bad47f93943eb8e4545934c9e1804e43b5d55e9':
Import revised translations. DO NOT MERGE
diff --git a/java/proguard.flags b/java/proguard.flags
index 9096855..729f4ad 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -14,3 +14,7 @@
void waitUntilUpdateDBDone();
void waitForDictionaryLoading();
}
+
+-keep class com.android.inputmethod.latin.AutoCorrection {
+ java.lang.CharSequence getAutoCorrectionWord();
+}
diff --git a/java/res/drawable-hdpi/key_hint_comma_holo.9.png b/java/res/drawable-hdpi/key_hint_comma_holo.9.png
index 47ae5ef..da0d6fd 100644
--- a/java/res/drawable-hdpi/key_hint_comma_holo.9.png
+++ b/java/res/drawable-hdpi/key_hint_comma_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/key_hint_comma_large_holo.9.png b/java/res/drawable-hdpi/key_hint_comma_large_holo.9.png
new file mode 100644
index 0000000..1f2f707
--- /dev/null
+++ b/java/res/drawable-hdpi/key_hint_comma_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/key_hint_minus_holo.9.png b/java/res/drawable-hdpi/key_hint_minus_holo.9.png
new file mode 100644
index 0000000..2c34ef9
--- /dev/null
+++ b/java/res/drawable-hdpi/key_hint_minus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/key_hint_minus_large_holo.9.png b/java/res/drawable-hdpi/key_hint_minus_large_holo.9.png
new file mode 100644
index 0000000..0df056e
--- /dev/null
+++ b/java/res/drawable-hdpi/key_hint_minus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/key_hint_underscore_holo.9.png b/java/res/drawable-hdpi/key_hint_underscore_holo.9.png
new file mode 100644
index 0000000..e4f2719
--- /dev/null
+++ b/java/res/drawable-hdpi/key_hint_underscore_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/key_hint_underscore_large_holo.9.png b/java/res/drawable-hdpi/key_hint_underscore_large_holo.9.png
new file mode 100644
index 0000000..dad34fc
--- /dev/null
+++ b/java/res/drawable-hdpi/key_hint_underscore_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-hdpi/sym_keyboard_settings_holo.png b/java/res/drawable-hdpi/sym_keyboard_settings_holo.png
index b3af0c6..471bd0b 100644
--- a/java/res/drawable-hdpi/sym_keyboard_settings_holo.png
+++ b/java/res/drawable-hdpi/sym_keyboard_settings_holo.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_comma_holo.9.png b/java/res/drawable-land-hdpi/key_hint_comma_holo.9.png
index 9ab5dad..da0d6fd 100644
--- a/java/res/drawable-land-hdpi/key_hint_comma_holo.9.png
+++ b/java/res/drawable-land-hdpi/key_hint_comma_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_comma_large_holo.9.png b/java/res/drawable-land-hdpi/key_hint_comma_large_holo.9.png
new file mode 100644
index 0000000..1f2f707
--- /dev/null
+++ b/java/res/drawable-land-hdpi/key_hint_comma_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_minus_holo.9.png b/java/res/drawable-land-hdpi/key_hint_minus_holo.9.png
new file mode 100644
index 0000000..2c34ef9
--- /dev/null
+++ b/java/res/drawable-land-hdpi/key_hint_minus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_minus_large_holo.9.png b/java/res/drawable-land-hdpi/key_hint_minus_large_holo.9.png
new file mode 100644
index 0000000..0df056e
--- /dev/null
+++ b/java/res/drawable-land-hdpi/key_hint_minus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_underscore_holo.9.png b/java/res/drawable-land-hdpi/key_hint_underscore_holo.9.png
new file mode 100644
index 0000000..e4f2719
--- /dev/null
+++ b/java/res/drawable-land-hdpi/key_hint_underscore_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-hdpi/key_hint_underscore_large_holo.9.png b/java/res/drawable-land-hdpi/key_hint_underscore_large_holo.9.png
new file mode 100644
index 0000000..dad34fc
--- /dev/null
+++ b/java/res/drawable-land-hdpi/key_hint_underscore_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_at_holo.9.png b/java/res/drawable-land-mdpi/key_hint_at_holo.9.png
index d1ea313..5b946ff 100644
--- a/java/res/drawable-land-mdpi/key_hint_at_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_at_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_at_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_at_large_holo.9.png
index 786bbc5..852f899 100644
--- a/java/res/drawable-land-mdpi/key_hint_at_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_at_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_colon_holo.9.png b/java/res/drawable-land-mdpi/key_hint_colon_holo.9.png
index 12ce267..1d9346e 100644
--- a/java/res/drawable-land-mdpi/key_hint_colon_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_colon_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_colon_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_colon_large_holo.9.png
index a51bada..17e9091 100644
--- a/java/res/drawable-land-mdpi/key_hint_colon_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_colon_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_comma_holo.9.png b/java/res/drawable-land-mdpi/key_hint_comma_holo.9.png
index f939162..c2a913c 100644
--- a/java/res/drawable-land-mdpi/key_hint_comma_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_comma_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_comma_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_comma_large_holo.9.png
new file mode 100644
index 0000000..846f213
--- /dev/null
+++ b/java/res/drawable-land-mdpi/key_hint_comma_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_exclamation_holo.9.png b/java/res/drawable-land-mdpi/key_hint_exclamation_holo.9.png
index a14623d..ce8e8de 100644
--- a/java/res/drawable-land-mdpi/key_hint_exclamation_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_exclamation_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_exclamation_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_exclamation_large_holo.9.png
index ce52d3a..035dcf8 100644
--- a/java/res/drawable-land-mdpi/key_hint_exclamation_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_exclamation_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_minus_holo.9.png b/java/res/drawable-land-mdpi/key_hint_minus_holo.9.png
new file mode 100644
index 0000000..e59a315
--- /dev/null
+++ b/java/res/drawable-land-mdpi/key_hint_minus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_minus_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_minus_large_holo.9.png
new file mode 100644
index 0000000..52c28dd
--- /dev/null
+++ b/java/res/drawable-land-mdpi/key_hint_minus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_plus_holo.9.png b/java/res/drawable-land-mdpi/key_hint_plus_holo.9.png
index a80c031..931390b 100644
--- a/java/res/drawable-land-mdpi/key_hint_plus_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_plus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_plus_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_plus_large_holo.9.png
index e8daaf0..e6f9f8a 100644
--- a/java/res/drawable-land-mdpi/key_hint_plus_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_plus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_question_holo.9.png b/java/res/drawable-land-mdpi/key_hint_question_holo.9.png
index 2b71d74..6cbeb59 100644
--- a/java/res/drawable-land-mdpi/key_hint_question_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_question_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_question_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_question_large_holo.9.png
index 0413368..bfd58de 100644
--- a/java/res/drawable-land-mdpi/key_hint_question_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_question_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_quote_holo.9.png b/java/res/drawable-land-mdpi/key_hint_quote_holo.9.png
index 486e5e1..3b361b7 100644
--- a/java/res/drawable-land-mdpi/key_hint_quote_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_quote_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_quote_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_quote_large_holo.9.png
index 4977031..2a08aa1 100644
--- a/java/res/drawable-land-mdpi/key_hint_quote_large_holo.9.png
+++ b/java/res/drawable-land-mdpi/key_hint_quote_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_underscore_holo.9.png b/java/res/drawable-land-mdpi/key_hint_underscore_holo.9.png
new file mode 100644
index 0000000..52e871e
--- /dev/null
+++ b/java/res/drawable-land-mdpi/key_hint_underscore_holo.9.png
Binary files differ
diff --git a/java/res/drawable-land-mdpi/key_hint_underscore_large_holo.9.png b/java/res/drawable-land-mdpi/key_hint_underscore_large_holo.9.png
new file mode 100644
index 0000000..ee0e835
--- /dev/null
+++ b/java/res/drawable-land-mdpi/key_hint_underscore_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_at_holo.9.png b/java/res/drawable-mdpi/key_hint_at_holo.9.png
index e596144..5b946ff 100644
--- a/java/res/drawable-mdpi/key_hint_at_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_at_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_at_large_holo.9.png b/java/res/drawable-mdpi/key_hint_at_large_holo.9.png
index 63d0714..852f899 100644
--- a/java/res/drawable-mdpi/key_hint_at_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_at_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_colon_holo.9.png b/java/res/drawable-mdpi/key_hint_colon_holo.9.png
index 12ce267..1d9346e 100644
--- a/java/res/drawable-mdpi/key_hint_colon_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_colon_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_colon_large_holo.9.png b/java/res/drawable-mdpi/key_hint_colon_large_holo.9.png
index a51bada..17e9091 100644
--- a/java/res/drawable-mdpi/key_hint_colon_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_colon_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_comma_holo.9.png b/java/res/drawable-mdpi/key_hint_comma_holo.9.png
index 82e4a93..c2a913c 100644
--- a/java/res/drawable-mdpi/key_hint_comma_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_comma_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png b/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png
new file mode 100644
index 0000000..846f213
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_comma_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_exclamation_holo.9.png b/java/res/drawable-mdpi/key_hint_exclamation_holo.9.png
index b57351b..ce8e8de 100644
--- a/java/res/drawable-mdpi/key_hint_exclamation_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_exclamation_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_exclamation_large_holo.9.png b/java/res/drawable-mdpi/key_hint_exclamation_large_holo.9.png
index a8a17eb..035dcf8 100644
--- a/java/res/drawable-mdpi/key_hint_exclamation_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_exclamation_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_minus_holo.9.png b/java/res/drawable-mdpi/key_hint_minus_holo.9.png
new file mode 100644
index 0000000..e59a315
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_minus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_minus_large_holo.9.png b/java/res/drawable-mdpi/key_hint_minus_large_holo.9.png
new file mode 100644
index 0000000..52c28dd
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_minus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_plus_holo.9.png b/java/res/drawable-mdpi/key_hint_plus_holo.9.png
index a80c031..931390b 100644
--- a/java/res/drawable-mdpi/key_hint_plus_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_plus_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_plus_large_holo.9.png b/java/res/drawable-mdpi/key_hint_plus_large_holo.9.png
index e8daaf0..e6f9f8a 100644
--- a/java/res/drawable-mdpi/key_hint_plus_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_plus_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_question_holo.9.png b/java/res/drawable-mdpi/key_hint_question_holo.9.png
index 9491d87..6cbeb59 100644
--- a/java/res/drawable-mdpi/key_hint_question_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_question_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_question_large_holo.9.png b/java/res/drawable-mdpi/key_hint_question_large_holo.9.png
index c9902ff..bfd58de 100644
--- a/java/res/drawable-mdpi/key_hint_question_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_question_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_quote_holo.9.png b/java/res/drawable-mdpi/key_hint_quote_holo.9.png
index a036421..3b361b7 100644
--- a/java/res/drawable-mdpi/key_hint_quote_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_quote_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_quote_large_holo.9.png b/java/res/drawable-mdpi/key_hint_quote_large_holo.9.png
index 5381b13..2a08aa1 100644
--- a/java/res/drawable-mdpi/key_hint_quote_large_holo.9.png
+++ b/java/res/drawable-mdpi/key_hint_quote_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_underscore_holo.9.png b/java/res/drawable-mdpi/key_hint_underscore_holo.9.png
new file mode 100644
index 0000000..52e871e
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_underscore_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/key_hint_underscore_large_holo.9.png b/java/res/drawable-mdpi/key_hint_underscore_large_holo.9.png
new file mode 100644
index 0000000..ee0e835
--- /dev/null
+++ b/java/res/drawable-mdpi/key_hint_underscore_large_holo.9.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_settings_holo.png b/java/res/drawable-mdpi/sym_keyboard_settings_holo.png
index 8233623..784a450 100644
--- a/java/res/drawable-mdpi/sym_keyboard_settings_holo.png
+++ b/java/res/drawable-mdpi/sym_keyboard_settings_holo.png
Binary files differ
diff --git a/java/res/drawable-mdpi/sym_keyboard_smiley_holo.png b/java/res/drawable-mdpi/sym_keyboard_smiley_holo.png
new file mode 100644
index 0000000..594fe21
--- /dev/null
+++ b/java/res/drawable-mdpi/sym_keyboard_smiley_holo.png
Binary files differ
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 0f0e76e..d1d70ff 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"المزيد"</string>
<string name="label_pause_key" msgid="181098308428035340">"توقف مؤقت"</string>
<string name="label_wait_key" msgid="6402152600878093134">"انتظار"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"حذف"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"رجوع"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"الإعدادات"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"العالي"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"مسافة"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"الرموز"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"الإدخال الصوتي"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"تشغيل الرموز"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"إيقاف الرموز"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"تشغيل العالي"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"إيقاف العالي"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"الإدخال الصوتي"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"الإدخال الصوتي غير معتمد حاليًا للغتك، ولكنه يعمل باللغة الإنجليزية."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"يستخدم الإدخال الصوتي خاصية التعرف على الكلام من Google. تنطبق "<a href="http://m.google.com/privacy">"سياسة خصوصية الجوال"</a>"."</string>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index 1743886..418bb18 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Още"</string>
<string name="label_pause_key" msgid="181098308428035340">"Пауза"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Чака"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Изтриване"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Настройки"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Интервал"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Символи"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Гласово въвеждане"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Символите са включени"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Символите са изключени"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift е включен"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift е изключен"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Гласово въвеждане"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"За вашия език понастоящем не се поддържа гласово въвеждане, но можете да го използвате на английски."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовото въвеждане използва функцията на Google за разпознаване на говор. В сила е "<a href="http://m.google.com/privacy">"Декларацията за поверителност за мобилни устройства"</a>"."</string>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 43c9749..1bfa3a9 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Suprimeix"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Retorn"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Configuració"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Majúscules"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espai"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbols"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulador"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Entrada de veu"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Símbols activats"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Símbols desactivats"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Majúscules activades"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Majúscules desactivades"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Entrada de veu"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualment, l\'entrada de veu no és compatible amb el vostre idioma, però funciona en anglès."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'entrada de veu utilitza el reconeixement de veu de Google. S\'hi aplica la "<a href="http://m.google.com/privacy">"Política de privadesa de Google Mobile"</a>"."</string>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 783c392..b0cb7da 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Smazat"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Nastavení"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"mezera"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboly"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Karta"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Hlasový vstup"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symboly jsou zapnuty"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symboly jsou vypnuty"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Režim Shift je zapnutý"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Režim Shift je vypnutý"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pro váš jazyk aktuálně není hlasový vstup podporován, ale funguje v angličtině."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používá rozpoznávání hlasu Google a vztahují se na něj "<a href="http://m.google.com/privacy">"Zásady ochrany osobních údajů pro mobilní služby"</a>"."</string>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 9722cda..eb79888 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Mere"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Vent"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Slet"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Tilbage"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Indstillinger"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Mellemrum"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Fane"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Stemmeinput"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symboler: Til"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symboler: Fra"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift: Til"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift: Fra"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Stemmeinput"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmeinput understøttes i øjeblikket ikke for dit sprog, men fungerer på engelsk."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Stemmeinput anvender Googles stemmegenkendelse. "<a href="http://m.google.com/privacy">"Fortrolighedspolitikken for mobilenheder"</a>" gælder."</string>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 7a7f3c5..396d74d 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Mehr"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Warten"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Löschen"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Eingabe"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Einstellungen"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Umschalt"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Leerzeichen"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbole"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Spracheingabe"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symbole an"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symbole aus"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Umschalt an"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Umschalt aus"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Spracheingabe"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spracheingaben werden derzeit nicht für Ihre Sprache unterstützt, funktionieren jedoch in Englisch."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Die Spracheingabe verwendet die Spracherkennung von Google. Es gelten die "<a href="http://m.google.com/privacy">"Google Mobile-Datenschutzbestimmungen"</a>"."</string>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index 59e9ff8..6d0869b 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Περισσότερα"</string>
<string name="label_pause_key" msgid="181098308428035340">"Παύση"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Αναμ."</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Ρυθμίσεις"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Κενό"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Σύμβολα"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Φωνητική εντολή"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Σύμβολα ενεργά"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Σύμβολα ανενεργά"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift ενεργό"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift ανενεργό"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Φωνητική είσοδος"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Η φωνητική είσοδος δεν υποστηρίζεται αυτή τη στιγμή για τη γλώσσα σας, ωστόσο λειτουργεί στα Αγγλικά."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Οι φωνητικές εντολές χρησιμοποιούν την τεχνολογία αναγνώρισης φωνής της Google. Ισχύει "<a href="http://m.google.com/privacy">"η Πολιτική Απορρήτου για κινητά"</a>"."</string>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 3af0182..b2e62b0 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Settings"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbols"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Voice Input"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symbols on"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symbols off"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift on"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift off"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Voice input"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Voice input is not currently supported for your language, but does work in English."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Voice input uses Google\'s speech recognition. "<a href="http://m.google.com/privacy">"The Mobile Privacy Policy"</a>" applies."</string>
diff --git a/java/res/values-en/donottranslate-altchars.xml b/java/res/values-en/donottranslate-altchars.xml
index 3950d7d..29582c9 100644
--- a/java/res/values-en/donottranslate-altchars.xml
+++ b/java/res/values-en/donottranslate-altchars.xml
@@ -22,6 +22,7 @@
<string name="alternates_for_e">3,è,é,ê,ë,ē</string>
<string name="alternates_for_i">8,î,ï,í,ī,ì</string>
<string name="alternates_for_o">9,ô,ö,ò,ó,œ,ø,ō,õ</string>
+ <string name="alternates_for_s">ß</string>
<string name="alternates_for_u">7,û,ü,ù,ú,ū</string>
<string name="alternates_for_n">ñ</string>
<string name="alternates_for_c">ç</string>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index c949986..02f36ff 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Eliminar"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Volver"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Configuración"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Mayús"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espacio"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos activados"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desactivados"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Mayús activado"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Mayús desactivado"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Entrada por voz"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La entrada por voz no está admitida en tu idioma, pero sí funciona en inglés."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz usa el reconocimiento de voz de Google. "<a href="http://m.google.com/privacy">"Se aplica la política de privacidad para"</a>" celulares."</string>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 0bd23a1..f9a86ff 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Eliminar"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Retroceso"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Ajustes"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Mayús"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espacio"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulador"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos activados"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desactivados"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Mayús activadas"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Mayús desactivadas"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Introducción de voz"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente la introducción de voz no está disponible en tu idioma, pero se puede utilizar en inglés."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La entrada de voz utiliza el reconocimiento de voz de Google. Se aplica la "<a href="http://m.google.com/privacy">"Política de privacidad de Google para móviles"</a>"."</string>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index dbcfecc..b594411 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"بیشتر"</string>
<string name="label_pause_key" msgid="181098308428035340">"توقف موقت"</string>
<string name="label_wait_key" msgid="6402152600878093134">"منتظر بمانید"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"تنظیمات"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"فاصله"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"نمادها"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"ورودی صوتی"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"نمادها روشن"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"نمادها خاموش"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift روشن"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift خاموش"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"ورودی صوتی"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ورودی صوتی در حال حاضر برای زبان شما پشتیبانی نمی شود اما برای زبان انگلیسی فعال است."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ورودی صوتی از تشخیص صدای Google استفاده می کند. "<a href="http://m.google.com/privacy">"خط مشی رازداری Mobile "</a>" اعمال می شود."</string>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index fe9aef9..0bf3855 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Lisää"</string>
<string name="label_pause_key" msgid="181098308428035340">"Tauko"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Odota"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Poista"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Rivinvaihto"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Asetukset"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Välilyönti"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbolit"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Sarkain"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Äänisyöte"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symbolit käytössä"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symbolit pois käytöstä"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift käytössä"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift pois käytöstä"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Äänisyöte"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Äänisyötettä ei vielä tueta kielelläsi, mutta voit käyttää sitä englanniksi."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Äänisyöte käyttää Googlen puheentunnistusta. "<a href="http://m.google.com/privacy">"Mobile-tietosuojakäytäntö"</a>" on voimassa."</string>
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index b79df7b..6c35362 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -19,7 +19,7 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Symbols that are commonly considered word separators in this language -->
- <string name="word_separators">.\u0009\u0020,;:!?\'\n()[]*&@{}/<>_+=|\u0022</string>
+ <string name="word_separators">.\u0009\u0020,;:!?\n()[]*&@{}/<>_+=|\u0022</string>
<!-- Symbols that are sentence separators, for purposes of making it hug the last sentence. -->
<string name="sentence_separators">.,</string>
</resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index 9f7cfef..b7aac94 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Plus"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Attente"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Supprimer"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Entrée"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Paramètres"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Maj"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espace"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboles"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulation"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Saisie vocale"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symboles activés"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symboles désactivés"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Maj activée"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Maj désactivée"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Saisie vocale"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"La saisie vocale n\'est pas encore prise en charge pour votre langue, mais elle fonctionne en anglais."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"La saisie vocale fait appel à la reconnaissance vocale de Google. Les "<a href="http://m.google.com/privacy">"Règles de confidentialité Google Mobile"</a>" s\'appliquent."</string>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index be70696..e6879a6 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Više"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pauza"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Pričekaj"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Postavke"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Razmaknica"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboli"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulator"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Glasovni unos"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simboli uključeni"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simboli isključeni"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift uključen"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift isključen"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Glasovni unos"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Vaš jezik trenutno nije podržan za glasovni unos, ali radi za engleski."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni unos upotrebljava Googleovo prepoznavanje govora. Primjenjuju se "<a href="http://m.google.com/privacy">"Pravila o privatnosti za uslugu Mobile"</a>"."</string>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index 05209c5..a8bf983 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Törlés"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Vissza"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Beállítások"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Szóköz"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Szimbólumok"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Hangbevitel"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Szimbólumok be"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Szimbólumok ki"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift be"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift ki"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Hangbevitel"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A hangbevitel szolgáltatás jelenleg nem támogatja az Ön nyelvét, ám angolul működik."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A hangbevitel a Google beszédfelismerő technológiáját használja, amelyre a "<a href="http://m.google.com/privacy">"Mobil adatvédelmi irányelvek"</a>" érvényesek."</string>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 1eabfd4..4df7f1e 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Hapus"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Setelan"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Spasi"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simbol"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Masukan Suara"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simbol hidup"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simbol mati"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift hidup"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift mati"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Masukan suara"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Masukan suara saat ini tidak didukung untuk bahasa Anda, tetapi bekerja dalam Bahasa Inggris."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Masukan suara menggunakan pengenalan ucapan Google. "<a href="http://m.google.com/privacy">"Kebijakan Privasi Seluler"</a>" berlaku."</string>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index ac0cb9e..9bf4f9e 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Cancella"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Invio"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Impostazioni"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Maiuscolo"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Spazio"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboli"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulazione"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Input vocale"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simboli attivati"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simboli disattivati"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Maiuscole attivate"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Maiuscole disattivate"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Comandi vocali"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"I comandi vocali non sono attualmente supportati per la tua lingua ma funzionano in inglese."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"L\'input vocale utilizza il riconoscimento vocale di Google. Sono valide le "<a href="http://m.google.com/privacy">"norme sulla privacy di Google Mobile"</a>"."</string>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index 6fd274b..af0854c 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"עוד"</string>
<string name="label_pause_key" msgid="181098308428035340">"השהה"</string>
<string name="label_wait_key" msgid="6402152600878093134">"המתן"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"מחק"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"חזור"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"הגדרות"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"רווח"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"סמלים"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"כרטיסייה"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"קלט קולי"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"מצב סמלים פועל"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"מצב סמלים כבוי"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift פועל"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift כבוי"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"קלט קולי"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"קלט קולי אינו נתמך בשלב זה בשפתך, אך הוא פועל באנגלית."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"קלט קולי משתמש בזיהוי דיבור של Google. "<a href="http://m.google.com/privacy">"מדיניות הפרטיות של \'Google לנייד\'"</a>" חלה במקרה זה."</string>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 0af3007..cd2cf80 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Shift"</string>
<string name="label_pause_key" msgid="181098308428035340">"停止"</string>
<string name="label_wait_key" msgid="6402152600878093134">"待機"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Del"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"設定"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"記号"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"音声入力"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"記号ON"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"記号OFF"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift ON"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift OFF"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"音声入力"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"音声入力は現在英語には対応していますが、日本語には対応していません。"</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"音声入力ではGoogleの音声認識技術を利用します。"<a href="http://m.google.com/privacy">"モバイルプライバシーポリシー"</a>"が適用されます。"</string>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index f6dde1b..7a09da8 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"더보기"</string>
<string name="label_pause_key" msgid="181098308428035340">"일시 중지"</string>
<string name="label_wait_key" msgid="6402152600878093134">"대기"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"삭제"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"리턴"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"설정"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"시프트"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"스페이스"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"기호"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"탭"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"음성 입력"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"기호 사용"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"기호 사용 안함"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"시프트 사용"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"시프트 사용 안함"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"음성 입력"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"음성 입력은 현재 자국어로 지원되지 않으며 영어로 작동됩니다."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"음성 입력에서는 Google의 음성 인식 기능을 사용합니다. "<a href="http://m.google.com/privacy">"모바일 개인정보취급방침"</a>"이 적용됩니다."</string>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index b1bc02a..c12c62a 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Daugiau"</string>
<string name="label_pause_key" msgid="181098308428035340">"Prist."</string>
<string name="label_wait_key" msgid="6402152600878093134">"Lauk."</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Ištrinti"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Grįžti"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Nustatymai"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Keitimas"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Tarpas"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboliai"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Skirtukas"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Balso įvestis"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simboliai įjungti"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simboliai išjungti"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Keitimas įjungtas"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Keitimas išjungtas"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Balso įvestis"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Šiuo metu balso įvestis jūsų kompiuteryje nepalaikoma, bet ji veikia anglų k."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balso įvesčiai naudojamas „Google“ kalbos atpažinimas. Taikoma "<a href="http://m.google.com/privacy">"privatumo politika mobiliesiems"</a>"."</string>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index d82a98f..8b975b0 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Vairāk"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pauze"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Gaidīt"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Dzēšanas taustiņš"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Atgriešanās taustiņš"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Iestatījumu taustiņš"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Pārslēgšanas taustiņš"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Atstarpes taustiņš"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simbolu taustiņš"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulēšanas taustiņš"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Runas ievades taustiņš"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simbolu režīms ir ieslēgts."</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simbolu režīms ir izslēgts."</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Pārslēgšanas režīms ir ieslēgts."</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Pārslēgšanas režīms ir izslēgts."</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Balss ievade"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Balss ievade jūsu valodā pašlaik netiek atbalstīta, taču tā ir pieejama angļu valodā."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Balss ievadei tiek izmantota Google runas atpazīšanas funkcija. Uz šīs funkcijas lietošanu attiecas "<a href="http://m.google.com/privacy">"mobilo sakaru ierīču lietošanas konfidencialitātes politika"</a>"."</string>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index c058580..0471e74 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Mer"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pause"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Vent"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Innstillinger"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Mellomrom"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Taleinndata"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symboler er slått på"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symboler er slått av"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift på"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift av"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Stemmedata"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Stemmedata håndteres foreløpig ikke på ditt språk, men fungerer på engelsk."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Google Voice bruker Googles talegjenkjenning. "<a href="http://m.google.com/privacy">"Personvernreglene for mobil"</a>" gjelder."</string>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 82d3e7e..e5439eb 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Instellingen"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Spatie"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbolen"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Spraakinvoer"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symbolen aan"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symbolen uit"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift aan"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift uit"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Spraakinvoer"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Spraakinvoer wordt momenteel niet ondersteund in uw taal, maar is wel beschikbaar in het Engels."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Spraakinvoer maakt gebruik van de spraakherkenning van Google. Het "<a href="http://m.google.com/privacy">"Privacybeleid van Google Mobile"</a>" is van toepassing."</string>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 10ba3af..633b159 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Ustawienia"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Spacja"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symbole"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Wprowadzanie głosowe"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symbole włączone"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symbole wyłączone"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift włączony"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift wyłączony"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Wprowadzanie głosowe"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Wprowadzanie głosowe obecnie nie jest obsługiwane w Twoim języku, ale działa w języku angielskim."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Funkcja wprowadzania głosowego wykorzystuje mechanizm rozpoznawania mowy. Obowiązuje "<a href="http://m.google.com/privacy">"Polityka prywatności Google Mobile"</a>"."</string>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 961eea9..f06d64c 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Definições"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espaço"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Entrada de voz"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos ativados"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desativados"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift ativado"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift desativado"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Actualmente, a entrada de voz não é suportada para o seu idioma, mas funciona em inglês."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de voz utiliza o reconhecimento de voz da Google. É aplicável a "<a href="http://m.google.com/privacy">"Política de privacidade do Google Mobile"</a>"."</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 7330a5c..9fc1a97 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Excluir"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Voltar"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Configurações"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Espaço"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Símbolos"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Entrada de texto por voz"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Símbolos ativados"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Símbolos desativados"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift ativado"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift desativado"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Entrada de voz"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"A entrada de voz não é suportada no momento para o seu idioma, mas funciona em inglês."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"A entrada de texto por voz usa o reconhecimento de voz do Google. "<a href="http://m.google.com/privacy">"A política de privacidade para celulares"</a>" é aplicada."</string>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index 3372d0f..57548b5 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -74,6 +74,30 @@
<skip />
<!-- no translation found for label_wait_key (6402152600878093134) -->
<skip />
+ <!-- no translation found for description_delete_key (5586406298531883960) -->
+ <skip />
+ <!-- no translation found for description_return_key (8750044000806461678) -->
+ <skip />
+ <!-- no translation found for description_settings_key (7484527796782969219) -->
+ <skip />
+ <!-- no translation found for description_shift_key (346906866277787836) -->
+ <skip />
+ <!-- no translation found for description_space_key (8512130111575878517) -->
+ <skip />
+ <!-- no translation found for description_switch_alpha_symbol_key (4537975384274405537) -->
+ <skip />
+ <!-- no translation found for description_tab_key (828186583738307137) -->
+ <skip />
+ <!-- no translation found for description_voice_key (3057731675774652754) -->
+ <skip />
+ <!-- no translation found for description_symbols_on (2994366855822840559) -->
+ <skip />
+ <!-- no translation found for description_symbols_off (3209578267079515136) -->
+ <skip />
+ <!-- no translation found for description_shift_on (6983188949895971587) -->
+ <skip />
+ <!-- no translation found for description_shift_off (8553265474523069034) -->
+ <skip />
<string name="voice_warning_title" msgid="4419354150908395008">"Cumonds vocals"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"\"Cumonds vocals en Vossa lingua na vegnan actualmain betg sustegnids, ma la funcziun è disponibla per englais.\""</string>
<!-- outdated translation 4611518823070986445 --> <string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ils cumonds vocals èn ina funcziunalitad experimentala che utilisescha la renconuschientscha vocala da rait da Google."</string>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 3045a62..52bed27 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Mai multe"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pauză"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Aşt."</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Ştergeţi"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Tasta Enter"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Setări"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Tasta Space"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simboluri"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tasta Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Intrare vocală"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simbolurile sunt activate"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simbolurile sunt dezactivate"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Tasta Shift este activată"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Tasta Shift este dezactivată"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Intrare voce"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Intrarea vocală nu este acceptată în prezent pentru limba dvs., însă funcţionează în limba engleză."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Intrarea vocală utilizează funcţia Google de recunoaştere vocală. Se aplică "<a href="http://m.google.com/privacy">"Politica de confidenţialitate Google Mobil"</a>"."</string>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 41b7f71..f8fdab0 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Ещё"</string>
<string name="label_pause_key" msgid="181098308428035340">"Приостановить"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Подождите"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Клавиша удаления"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Клавиша \"Ввод\""</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Клавиша настроек"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Клавиша верхнего регистра"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Клавиша \"Пробел\""</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Клавиша символов"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Клавиша табуляции"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Клавиша голосового ввода"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Клавиши символов выключены"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Клавиши символов включены"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Верхний регистр включен"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Верхний регистр выключен"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Голосовой ввод"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"В настоящее время функция голосового ввода не поддерживает ваш язык, но вы можете пользоваться ей на английском."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовой ввод использует алгоритмы распознавания речи Google. Действует "<a href="http://m.google.com/privacy">"политика конфиденциальности для мобильных устройств"</a>"."</string>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index d14783e..569f273 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Nastavenia"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Medzera"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboly"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Hlasový vstup"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Symboly zapnuté"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Symboly vypnuté"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift zapnutý"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift vypnutý"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Hlasový vstup"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Pre váš jazyk aktuálne nie je hlasový vstup podporovaný, ale funguje v angličtine."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Hlasový vstup používa rozpoznávanie hlasu Google. Na používanie hlasového vstupu sa vzťahujú "<a href="http://m.google.com/privacy">"Pravidlá ochrany osobných údajov pre mobilné služby"</a>"."</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 224cb4c..715e6c5 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Več"</string>
<string name="label_pause_key" msgid="181098308428035340">"Premor"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Čakaj"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Izbriši"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Vračalka"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Nastavitve"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Dvigalka"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Preslednica"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Znaki"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabulatorka"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Glasovni vnos"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Znaki vklopljeni"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Znaki izklopljeni"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Dvigalka vklopljena"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Dvigalka izklopljena"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Glasovni vnos"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Glasovni vnos trenutno ni podprt v vašem jeziku, deluje pa v angleščini."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Glasovni vnos uporablja Googlovo prepoznavanje govora. Zanj velja "<a href="http://m.google.com/privacy">"pravilnik o zasebnosti za mobilne naprave"</a>"."</string>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index fbcb31a..1157327 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Још"</string>
<string name="label_pause_key" msgid="181098308428035340">"Паузирај"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Сачекајте"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Подешавања"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Размак"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Симболи"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Гласовни унос"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Симболи су укључени"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Симболи су искључени"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift је укључен"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift је искључен"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Гласовни унос"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Гласовни унос тренутно није подржан за ваш језик, али функционише на енглеском."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Гласовни унос користи Google-ову функцију за препознавање гласа. Примењује се "<a href="http://m.google.com/privacy">"политика приватности за мобилне уређаје"</a>"."</string>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index 01cc52c..b8c62f4 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Mer"</string>
<string name="label_pause_key" msgid="181098308428035340">"Pausa"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Vänta"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Ta bort"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Retur"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Inställningar"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Skift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Blanksteg"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Symboler"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tabb"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Röstinmatning"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Aktivera symboler"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Inaktivera symboler"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Aktivera Skift"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Inaktivera Skift"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Röstindata"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Röstindata stöds inte på ditt språk än, men tjänsten fungerar på engelska."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Röstinmatning använder sig av Googles tjänst för taligenkänning. "<a href="http://m.google.com/privacy">"Sekretesspolicyn för mobila enheter"</a>" gäller."</string>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 7d4c1e3..836b987 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"เพิ่มเติม"</string>
<string name="label_pause_key" msgid="181098308428035340">"หยุดชั่วคราว"</string>
<string name="label_wait_key" msgid="6402152600878093134">"รอ"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"ลบ"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"การตั้งค่า"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Space"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"สัญลักษณ์"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"ป้อนข้อมูลด้วยเสียง"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"สัญลักษณ์เปิดอยู่"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"สัญลักษณ์ปิดอยู่"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift เปิดอยู่"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift ปิดอยู่"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"การป้อนข้อมูลด้วยเสียง"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"ขณะนี้การป้อนข้อมูลด้วยเสียงยังไม่ได้รับการสนับสนุนในภาษาของคุณ แต่ใช้ได้ในภาษาอังกฤษ"</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"ป้อนข้อมูลด้วยเสียงใช้การจดจำคำพูดของ Google "<a href="http://m.google.com/privacy">" นโยบายส่วนบุคคลของมือถือ"</a>"มีผลบังคับใช้"</string>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index 7707696..55f98de 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Tanggalin"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Bumalik"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Mga Setting"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Puwang"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Mga Simbolo"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Input ng Boses"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Naka-on ang mga simbolo"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Naka-off ang mga simbolo"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Naka-on ang shift"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Naka-off ang shift"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Pag-input ng boses"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Hindi kasalukuyang suportado ang pag-input ng boses para sa iyong wika, ngunit gumagana sa Ingles."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Gumagamit ang pag-input ng boses ng speech recognition ng Google. Nalalapat "<a href="http://m.google.com/privacy">"Ang Patakaran sa Privacy ng Mobile"</a>"."</string>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index 8ce04ff..71a6215 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Diğer"</string>
<string name="label_pause_key" msgid="181098308428035340">"Durkl"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Bekle"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Sil"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Ayarlar"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Üst Karakter"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Boşluk"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Simgeler"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Sekme"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Ses Girişi"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Simgeler açık"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Simgeler kapalı"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Üst Karakter açık"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Üst Karakter kapalı"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Ses girişi"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Ses girişi, şu anda sizin diliniz için desteklenmiyor ama İngilizce dilinde kullanılabilir."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Ses girişi Google\'ın konuşma tanıma işlevini kullanır. "<a href="http://m.google.com/privacy">" Mobil Gizlilik Politikası"</a>" geçerlidir."</string>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index c662b13..2690632 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"Більше"</string>
<string name="label_pause_key" msgid="181098308428035340">"Пауза"</string>
<string name="label_wait_key" msgid="6402152600878093134">"Чек."</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"Клавіша Delete"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Клавіша Return"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Клавіша Settings"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Клавіша Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Клавіша Space"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Клавіша Symbols"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Клавіша Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Клавіша Voice Input"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Символи ввімкнено"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Символи вимкнено"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift увімкнено"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift вимкнено"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Голос. ввід"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Голос. ввід наразі не підтрим. для вашої мови, але можна користуватися англійською."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Голосовий ввід використовує розпізнавання мовлення Google. Застосовується "<a href="http://m.google.com/privacy">"Політика конфіденційності для мобільних пристроїв"</a>"."</string>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index 1a141ec..70defe3 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -56,6 +56,18 @@
<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>
+ <string name="description_delete_key" msgid="5586406298531883960">"Xóa"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"Quay lại"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"Cài đặt"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"Dấu cách"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"Biểu tượng"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"Nhập liệu bằng giọng nói"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"Bật biểu tượng"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"Tắt biểu tượng"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Bật Shift"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Tắt Shift"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"Nhập liệu bằng giọng nói"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"Nhập liệu bằng giọng nói hiện không được hỗ trợ cho ngôn ngữ của bạn nhưng hoạt động với ngôn ngữ tiếng Anh."</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"Nhập liệu bằng giọng nói sử dụng nhận dạng giọng nói của Google. Áp dụng "<a href="http://m.google.com/privacy">"Chính sách bảo mật dành cho điện thoại di động"</a>"."</string>
diff --git a/java/res/values-xlarge/config.xml b/java/res/values-xlarge/config.xml
index 40fdce0..f075b1b 100644
--- a/java/res/values-xlarge/config.xml
+++ b/java/res/values-xlarge/config.xml
@@ -32,6 +32,7 @@
<bool name="config_digit_popup_characters_enabled">false</bool>
<!-- Whether or not Popup on key press is enabled by default -->
<bool name="config_default_popup_preview">false</bool>
+ <bool name="config_default_sound_enabled">true</bool>
<bool name="config_use_spacebar_language_switcher">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">true</bool>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index dacd93c..3b092bf 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"更多"</string>
<string name="label_pause_key" msgid="181098308428035340">"暂停"</string>
<string name="label_wait_key" msgid="6402152600878093134">"等待"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"删除"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"回车"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"设置"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"空格"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"符号"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"语音输入"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"符号模式已打开"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"符号模式已关闭"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"Shift 模式已打开"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"Shift 模式已关闭"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"语音输入"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"语音输入功能当前还不支持您的语言,您只能输入英语语音。"</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"语音输入采用了 Google 的语音识别技术,因此请遵守"<a href="http://m.google.com/privacy">"“Google 移动”隐私权政策"</a>"。"</string>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index acb1451..c9c7ae8 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -56,6 +56,18 @@
<string name="label_more_key" msgid="3760239494604948502">"更多"</string>
<string name="label_pause_key" msgid="181098308428035340">"暫停"</string>
<string name="label_wait_key" msgid="6402152600878093134">"等候"</string>
+ <string name="description_delete_key" msgid="5586406298531883960">"刪除"</string>
+ <string name="description_return_key" msgid="8750044000806461678">"返回"</string>
+ <string name="description_settings_key" msgid="7484527796782969219">"設定"</string>
+ <string name="description_shift_key" msgid="346906866277787836">"Shift 鍵"</string>
+ <string name="description_space_key" msgid="8512130111575878517">"空白鍵"</string>
+ <string name="description_switch_alpha_symbol_key" msgid="4537975384274405537">"符號"</string>
+ <string name="description_tab_key" msgid="828186583738307137">"Tab 鍵"</string>
+ <string name="description_voice_key" msgid="3057731675774652754">"語音輸入"</string>
+ <string name="description_symbols_on" msgid="2994366855822840559">"開啟符號"</string>
+ <string name="description_symbols_off" msgid="3209578267079515136">"關閉符號"</string>
+ <string name="description_shift_on" msgid="6983188949895971587">"開啟位移"</string>
+ <string name="description_shift_off" msgid="8553265474523069034">"關閉位移"</string>
<string name="voice_warning_title" msgid="4419354150908395008">"語音輸入"</string>
<string name="voice_warning_locale_not_supported" msgid="637923019716442333">"語音輸入目前不支援您的語言,但是可以辨識英文。"</string>
<string name="voice_warning_may_not_understand" msgid="5596289095878251072">"語音輸入使用 Google 的語音辨識功能,並遵循《"<a href="http://m.google.com/privacy">"行動服務隱私權政策"</a>"》。"</string>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index 9759e0e..f0da274 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -140,6 +140,8 @@
<attr name="keyStyle" format="string" />
<!-- Shift key icon for shifted state -->
<attr name="shiftedIcon" format="reference" />
+ <!-- The key is enabled and responds on press. -->
+ <attr name="enabled" format="boolean" />
</declare-styleable>
<declare-styleable name="Keyboard_Row">
@@ -166,10 +168,11 @@
<enum name="web" value="4" />
<enum name="phone" value="5" />
</attr>
+ <attr name="passwordInput" format="boolean" />
<attr name="hasSettingsKey" format="string" />
<attr name="voiceKeyEnabled" format="string" />
<attr name="hasVoiceKey" format="string" />
- <attr name="imeOptions">
+ <attr name="imeAction">
<!-- This should be aligned with EditorInfo.IME_ACTION_* -->
<flag name="actionUnspecified" value="0" />
<flag name="actionNone" value="1" />
@@ -180,6 +183,8 @@
<flag name="actionDone" value="6" />
<flag name="actionPrevious" value="7" />
</attr>
+ <attr name="languageCode" format="string" />
+ <attr name="countryCode" format="string" />
</declare-styleable>
<declare-styleable name="Keyboard_KeyStyle">
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index ceb4f12..bdb4409 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -20,7 +20,6 @@
<resources>
<bool name="config_swipeDisambiguation">true</bool>
- <bool name="default_recorrection_enabled">true</bool>
<bool name="config_long_press_comma_for_settings_enabled">true</bool>
<bool name="config_enable_show_settings_key_option">true</bool>
<bool name="config_enable_show_subtype_settings">true</bool>
@@ -39,6 +38,8 @@
<!-- Default values for whether quick fixes and bigram suggestions are activated -->
<bool name="config_default_quick_fixes">true</bool>
<bool name="config_default_bigram_suggestions">true</bool>
+ <bool name="config_default_recorrection_enabled">true</bool>
+ <bool name="config_default_sound_enabled">false</bool>
<bool name="config_use_spacebar_language_switcher">true</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>
@@ -59,6 +60,7 @@
<integer name="config_long_press_key_timeout">400</integer>
<integer name="config_long_press_shift_key_timeout">1200</integer>
<integer name="config_touch_noise_threshold_millis">40</integer>
+ <integer name="config_double_spaces_turn_into_period_timeout">1100</integer>
<dimen name="config_touch_noise_threshold_distance">2.0mm</dimen>
<!-- This configuration is the index of the array {@link KeyboardSwitcher.KEYBOARD_THEMES}. -->
<string name="config_default_keyboard_theme_id" translatable="false">4</string>
diff --git a/java/res/values/donottranslate-altchars.xml b/java/res/values/donottranslate-altchars.xml
index 4b1a6ae..518e74a 100644
--- a/java/res/values/donottranslate-altchars.xml
+++ b/java/res/values/donottranslate-altchars.xml
@@ -44,6 +44,9 @@
<string name="alternates_for_scandinavia_row2_11"></string>
<string name="alternates_for_cyrillic_e"></string>
<string name="alternates_for_cyrillic_soft_sign"></string>
+ <string name="alternates_for_currency_dollar">¢,£,€,¥,₱</string>
+ <string name="alternates_for_currency_euro">¢,£,$,¥,₱</string>
+ <string name="alternates_for_currency_pound">¢,$,€,¥,₱</string>
<string name="alternates_for_mic">"\@drawable/sym_keyboard_settings|\@integer/key_settings,\@drawable/sym_keyboard_mic|\@integer/key_voice"</string>
<string name="alternates_for_smiley">":-)|:-) ,:-(|:-( ,;-)|;-) ,:-P|:-P ,=-O|=-O ,:-*|:-* ,:O|:O ,B-)|B-) ,:-$|:-$ ,:-!|:-! ,:-[|:-[ ,O:-)|O:-) ,:-\\\\\\\\|:-\\\\\\\\ ,:\'(|:\'( ,:-D|:-D "</string>
<string name="alternates_for_settings_slash">"\@drawable/sym_keyboard_settings|\@integer/key_settings,/"</string>
diff --git a/java/res/values/keycodes.xml b/java/res/values/keycodes.xml
index 6c18cb4..d6f9bfc 100644
--- a/java/res/values/keycodes.xml
+++ b/java/res/values/keycodes.xml
@@ -28,4 +28,24 @@
<integer name="key_delete">-5</integer>
<integer name="key_settings">-100</integer>
<integer name="key_voice">-102</integer>
+
+ <!-- Array used for mapping key codes to description strings. -->
+ <array name="key_descriptions">
+ <item>@integer/key_tab</item>
+ <item>@string/description_tab_key</item>
+ <item>@integer/key_return</item>
+ <item>@string/description_return_key</item>
+ <item>@integer/key_space</item>
+ <item>@string/description_space_key</item>
+ <item>@integer/key_shift</item>
+ <item>@string/description_shift_key</item>
+ <item>@integer/key_switch_alpha_symbol</item>
+ <item>@string/description_switch_alpha_symbol_key</item>
+ <item>@integer/key_delete</item>
+ <item>@string/description_delete_key</item>
+ <item>@integer/key_settings</item>
+ <item>@string/description_settings_key</item>
+ <item>@integer/key_voice</item>
+ <item>@string/description_voice_key</item>
+ </array>
</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index f63d681..3c0a9c1 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -102,6 +102,31 @@
<!-- Label for "Wait" key of phone number keyboard. Must be short to fit on key! [CHAR LIMIT=5]-->
<string name="label_wait_key">Wait</string>
+ <!-- Spoken text description for delete key. -->
+ <string name="description_delete_key">Delete</string>
+ <!-- Spoken text description for return key. -->
+ <string name="description_return_key">Return</string>
+ <!-- Spoken text description for settings key. -->
+ <string name="description_settings_key">Settings</string>
+ <!-- Spoken text description for shift key. -->
+ <string name="description_shift_key">Shift</string>
+ <!-- Spoken text description for space key. -->
+ <string name="description_space_key">Space</string>
+ <!-- Spoken text description for symbols key. -->
+ <string name="description_switch_alpha_symbol_key">Symbols</string>
+ <!-- Spoken text description for tab key. -->
+ <string name="description_tab_key">Tab</string>
+ <!-- Spoken text description for voice input key. -->
+ <string name="description_voice_key">Voice Input</string>
+ <!-- Spoken text description for symbols mode on. -->
+ <string name="description_symbols_on">Symbols on</string>
+ <!-- Spoken text description for symbols mode off. -->
+ <string name="description_symbols_off">Symbols off</string>
+ <!-- Spoken text description for shift mode on. -->
+ <string name="description_shift_on">Shift on</string>
+ <!-- Spoken text description for shift mode off. -->
+ <string name="description_shift_off">Shift off</string>
+
<!-- Voice related labels -->
<!-- Title of the warning dialog that shows when a user initiates voice input for
diff --git a/java/res/values/whitelist.xml b/java/res/values/whitelist.xml
new file mode 100644
index 0000000..ced52e7
--- /dev/null
+++ b/java/res/values/whitelist.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!--
+ An entry of the whitelist word should be:
+ 1. (int)frequency
+ 2. (String)before
+ 3. (String)after
+ -->
+ <string-array name="wordlist_whitelist">
+
+ <item>255</item>
+ <item>ill</item>
+ <item>I\'ll</item>
+
+ <item>255</item>
+ <item>thisd</item>
+ <item>this\'d</item>
+
+ </string-array>
+</resources>
diff --git a/java/res/xml-xlarge/kbd_key_styles.xml b/java/res/xml-xlarge/kbd_key_styles.xml
index d211e5e..fc06d00 100644
--- a/java/res/xml-xlarge/kbd_key_styles.xml
+++ b/java/res/xml-xlarge/kbd_key_styles.xml
@@ -165,4 +165,19 @@
latin:keyOutputText="@string/keylabel_for_popular_domain"
latin:keyHintIcon="@drawable/hint_popup_holo"
latin:popupCharacters="@string/alternates_for_popular_domain" />
+ <switch>
+ <case
+ latin:passwordInput="true"
+ >
+ <key-style
+ latin:styleName="nonPasswordSymbolKeyStyle"
+ latin:enabled="false" />
+ </case>
+ <!-- latin:passwordInput="false" -->
+ <default>
+ <key-style
+ latin:styleName="nonPasswordSymbolKeyStyle"
+ latin:enabled="true" />
+ </default>
+ </switch>
</merge>
diff --git a/java/res/xml-xlarge/kbd_number.xml b/java/res/xml-xlarge/kbd_number.xml
index 875548b..012b751 100644
--- a/java/res/xml-xlarge/kbd_number.xml
+++ b/java/res/xml-xlarge/kbd_number.xml
@@ -31,120 +31,199 @@
>
<include
latin:keyboardLayout="@xml/kbd_key_styles" />
- <!-- This row is intentionally not marked as a top row -->
- <Row>
- <Key
- latin:keyStyle="tabKeyStyle"
- latin:keyLabelOption="alignLeft"
- latin:keyEdgeFlags="left" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="-"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="+"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="."
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="1" />
- <Key
- latin:keyLabel="2" />
- <Key
- latin:keyLabel="3" />
- <Spacer
- latin:horizontalGap="9.360%p" />
- <Key
- latin:keyStyle="deleteKeyStyle"
- latin:keyWidth="9.804%p"
- latin:keyEdgeFlags="right" />
- </Row>
- <Row>
- <Spacer
- latin:horizontalGap="16.406%p" />
- <Key
- latin:keyLabel="*"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="/"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel=","
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="4" />
- <Key
- latin:keyLabel="5" />
- <Key
- latin:keyLabel="6" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyStyle="returnKeyStyle"
- latin:keyWidth="14.706%p"
- latin:keyEdgeFlags="right" />
- </Row>
- <Row>
- <!-- There is an empty area bellow the "More" key and left of the "(" key. To ignore
- the touch event on the area, "(" is intentionally not marked as a left edge key. -->
- <Spacer
- latin:horizontalGap="16.406%p" />
- <Key
- latin:keyLabel="("
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel=")"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyLabel="="
- latin:keyWidth="8.042%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="7" />
- <Key
- latin:keyLabel="8" />
- <Key
- latin:keyLabel="9" />
- <!-- There is an empty area bellow the "Enter" key and right of the "9" key. To ignore
- the touch event on the area, "9" is intentionally not marked as a right edge key. -->
- </Row>
- <!-- This row is intentionally not marked as a bottom row -->
- <Row>
- <!-- There is an empty area bellow the "More" key and left of the "space" key. To ignore
- the touch event on the area, "space" is intentionally not marked as a left edge key. -->
- <Spacer
- latin:horizontalGap="8.362%p" />
- <Key
- latin:keyStyle="settingsKeyStyle"
- latin:keyWidth="8.042%p" />
- <Key
- latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
- latin:keyWidth="24.127%p" />
- <Spacer
- latin:horizontalGap="4.458%p" />
- <Key
- latin:keyLabel="*" />
- <Key
- latin:keyLabel="0" />
- <Key
- latin:keyLabel="#" />
- <switch>
- <case
- latin:voiceKeyEnabled="true"
- >
+ <include
+ latin:keyboardLayout="@xml/kbd_numkey_styles" />
+ <switch>
+ <case
+ latin:passwordInput="true"
+ >
+ <!-- This row is intentionally not marked as a top row -->
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
<Key
- latin:keyStyle="micKeyStyle"
+ latin:keyStyle="num1KeyStyle" />
+ <Key
+ latin:keyStyle="num2KeyStyle" />
+ <Key
+ latin:keyStyle="num3KeyStyle" />
+ <Spacer
+ latin:horizontalGap="22.272%p" />
+ <Key
+ latin:keyStyle="deleteKeyStyle"
+ latin:keyWidth="9.804%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
+ <Key
+ latin:keyStyle="num4KeyStyle" />
+ <Key
+ latin:keyStyle="num5KeyStyle" />
+ <Key
+ latin:keyStyle="num6KeyStyle" />
+ <Spacer
+ latin:horizontalGap="17.371%p" />
+ <Key
+ latin:keyStyle="returnKeyStyle"
+ latin:keyWidth="14.706%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="32.076%p" />
+ <Key
+ latin:keyStyle="num7KeyStyle" />
+ <Key
+ latin:keyStyle="num8KeyStyle" />
+ <Key
+ latin:keyStyle="num9KeyStyle" />
+ <!-- There is an empty area below the "Enter" key and right of the "9" key. To
+ ignore the touch event on the area, "9" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ <!-- This row is intentionally not marked as a bottom row -->
+ <Row>
+ <Spacer
+ latin:horizontalGap="44.026%p" />
+ <Key
+ latin:keyStyle="num0KeyStyle" />
+ <!-- There is an empty area below the "Enter" key and right of the "#" key. To
+ ignore the touch event on the area, "#" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ </case>
+ <!-- latin:passwordInput="false" -->
+ <default>
+ <!-- This row is intentionally not marked as a top row -->
+ <Row>
+ <Key
+ latin:keyStyle="tabKeyStyle"
+ latin:keyLabelOption="alignLeft"
+ latin:keyEdgeFlags="left" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="-"
latin:keyWidth="8.042%p" />
- </case>
- </switch>
- <!-- There is an empty area bellow the "Enter" key and right of the "#" key. To ignore
- the touch event on the area, "#" is intentionally not marked as a right edge key. -->
- </Row>
+ <Key
+ latin:keyLabel="+"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="."
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="1" />
+ <Key
+ latin:keyLabel="2" />
+ <Key
+ latin:keyLabel="3" />
+ <Spacer
+ latin:horizontalGap="9.360%p" />
+ <Key
+ latin:keyStyle="deleteKeyStyle"
+ latin:keyWidth="9.804%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <Spacer
+ latin:horizontalGap="16.406%p" />
+ <Key
+ latin:keyLabel="*"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="/"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel=","
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="4" />
+ <Key
+ latin:keyLabel="5" />
+ <Key
+ latin:keyLabel="6" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyStyle="returnKeyStyle"
+ latin:keyWidth="14.706%p"
+ latin:keyEdgeFlags="right" />
+ </Row>
+ <Row>
+ <!-- There is an empty area below the "More" key and left of the "(" key. To
+ ignore the touch event on the area, "(" is intentionally not marked as a left
+ edge key. -->
+ <Spacer
+ latin:horizontalGap="16.406%p" />
+ <Key
+ latin:keyLabel="("
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel=")"
+ latin:keyWidth="8.042%p" />
+ <Key
+ latin:keyLabel="="
+ latin:keyWidth="8.042%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="7" />
+ <Key
+ latin:keyLabel="8" />
+ <Key
+ latin:keyLabel="9" />
+ <!-- There is an empty area below the "Enter" key and right of the "9" key. To
+ ignore the touch event on the area, "9" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ <!-- This row is intentionally not marked as a bottom row -->
+ <Row>
+ <!-- There is an empty area below the "More" key and left of the "space" key. To
+ ignore the touch event on the area, "space" is intentionally not marked as a
+ left edge key. -->
+ <Spacer
+ latin:horizontalGap="8.362%p" />
+ <switch>
+ <case latin:hasSettingsKey="true">
+ <Key
+ latin:keyStyle="settingsKeyStyle"
+ latin:keyWidth="8.042%p" />
+ </case>
+ <default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
+ <Key
+ latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
+ latin:keyWidth="24.127%p" />
+ <Spacer
+ latin:horizontalGap="4.458%p" />
+ <Key
+ latin:keyLabel="*" />
+ <Key
+ latin:keyLabel="0" />
+ <Key
+ latin:keyLabel="#" />
+ <switch>
+ <case
+ latin:voiceKeyEnabled="true"
+ >
+ <Key
+ latin:keyStyle="micKeyStyle"
+ latin:keyWidth="8.042%p" />
+ </case>
+ </switch>
+ <!-- There is an empty area below the "Enter" key and right of the "#" key. To
+ ignore the touch event on the area, "#" is intentionally not marked as a right
+ edge key. -->
+ </Row>
+ </default>
+ </switch>
</Keyboard>
diff --git a/java/res/xml-xlarge/kbd_phone.xml b/java/res/xml-xlarge/kbd_phone.xml
index b9444ad..9122176 100644
--- a/java/res/xml-xlarge/kbd_phone.xml
+++ b/java/res/xml-xlarge/kbd_phone.xml
@@ -129,9 +129,17 @@
the touch event on the area, "space" is intentionally not marked as a left edge key. -->
<Spacer
latin:horizontalGap="12.340%p" />
- <Key
- latin:keyStyle="settingsKeyStyle"
- latin:keyWidth="8.042%p" />
+ <switch>
+ <case latin:hasSettingsKey="true">
+ <Key
+ latin:keyStyle="settingsKeyStyle"
+ latin:keyWidth="8.042%p" />
+ </case>
+ <default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
<Key
latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
latin:keyWidth="16.084%p" />
diff --git a/java/res/xml-xlarge/kbd_phone_symbols.xml b/java/res/xml-xlarge/kbd_phone_symbols.xml
index 690bcde..055c148 100644
--- a/java/res/xml-xlarge/kbd_phone_symbols.xml
+++ b/java/res/xml-xlarge/kbd_phone_symbols.xml
@@ -141,9 +141,17 @@
the touch event on the area, "space" is intentionally not marked as a left edge key. -->
<Spacer
latin:horizontalGap="8.362%p" />
- <Key
- latin:keyStyle="settingsKeyStyle"
- latin:keyWidth="8.042%p" />
+ <switch>
+ <case latin:hasSettingsKey="true">
+ <Key
+ latin:keyStyle="settingsKeyStyle"
+ latin:keyWidth="8.042%p" />
+ </case>
+ <default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
<Key
latin:keyStyle="nonSpecialBackgroundSpaceKeyStyle"
latin:keyWidth="24.127%p" />
diff --git a/java/res/xml-xlarge/kbd_qwerty_row4.xml b/java/res/xml-xlarge/kbd_qwerty_row4.xml
index 9d0fd81..f36b61f 100644
--- a/java/res/xml-xlarge/kbd_qwerty_row4.xml
+++ b/java/res/xml-xlarge/kbd_qwerty_row4.xml
@@ -27,29 +27,36 @@
>
<Spacer
latin:horizontalGap="8.362%p" />
- <Key
- latin:keyStyle="settingsKeyStyle" />
<switch>
- <case
- latin:mode="email"
- >
+ <case latin:hasSettingsKey="true">
<Key
- latin:keyStyle="comKeyStyle" />
- <Key
- latin:keyLabel="\@" />
- </case>
- <!-- TODO: implement logical OR for <case> attribute -->
- <case
- latin:mode="url"
- >
- <Key
- latin:keyStyle="comKeyStyle"
- latin:keyWidth="16.084%p" />
+ latin:keyStyle="settingsKeyStyle" />
</case>
<default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
<switch>
+ <!-- TODO: implement logical OR for <case> attribute -->
<case
- latin:imeOptions="actionSearch"
+ latin:mode="email"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:imeAction="actionSearch"
>
<Key
latin:keyLabel=":"
@@ -63,12 +70,84 @@
latin:keyStyle="smileyKeyStyle" />
</default>
</switch>
- <Key
- latin:keyLabel="/"
- latin:manualTemporaryUpperCaseCode="64"
- latin:keyHintIcon="@drawable/key_hint_at_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
- latin:popupCharacters="\@" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="\@" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="-"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="64"
+ latin:keyHintIcon="@drawable/key_hint_at_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
+ latin:popupCharacters="\@" />
+ </default>
+ </switch>
+ </case>
+ <!-- not languageCode="ru" -->
+ <default>
+ <switch>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle"
+ latin:keyWidth="16.084%p" />
+ </case>
+ <default>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyStyle="comKeyStyle" />
+ </case>
+ <case
+ latin:imeAction="actionSearch"
+ >
+ <Key
+ latin:keyLabel=":"
+ latin:manualTemporaryUpperCaseCode="43"
+ latin:keyHintIcon="@drawable/key_hint_plus_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_plus_large_holo"
+ latin:popupCharacters="+" />
+ </case>
+ <default>
+ <Key
+ latin:keyStyle="smileyKeyStyle" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="\@" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="64"
+ latin:keyHintIcon="@drawable/key_hint_at_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_at_large_holo"
+ latin:popupCharacters="\@" />
+ </default>
+ </switch>
+ </default>
+ </switch>
</default>
</switch>
<Key
@@ -76,44 +155,95 @@
latin:keyWidth="37.454%p" />
<switch>
<case
- latin:mode="email"
+ latin:languageCode="ru"
>
- <Key
- latin:keyLabel="-" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="58"
+ latin:keyHintIcon="@drawable/key_hint_colon_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
+ latin:popupCharacters=":" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="\?"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="!"
+ latin:manualTemporaryUpperCaseCode="39"
+ latin:keyHintIcon="@drawable/key_hint_quote_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
+ latin:popupCharacters="\'" />
+ </default>
+ </switch>
</case>
- <case
- latin:mode="url"
- >
- <Key
- latin:keyLabel="/"
- latin:manualTemporaryUpperCaseCode="58"
- latin:keyHintIcon="@drawable/key_hint_colon_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
- latin:popupCharacters=":" />
- </case>
+ <!-- not languageCode="ru" -->
<default>
- <Key
- latin:keyLabel="\'"
- latin:manualTemporaryUpperCaseCode="34"
- latin:keyHintIcon="@drawable/key_hint_quote_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
- latin:popupCharacters=""" />
- </default>
- </switch>
- <switch>
- <case
- latin:mode="email"
- >
- <Key
- latin:keyLabel="_" />
- </case>
- <default>
- <Key
- latin:keyLabel="-"
- latin:manualTemporaryUpperCaseCode="95"
- latin:keyHintIcon="@drawable/key_hint_underline_holo"
- latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
- latin:popupCharacters="_" />
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="/"
+ latin:manualTemporaryUpperCaseCode="58"
+ latin:keyHintIcon="@drawable/key_hint_colon_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_colon_large_holo"
+ latin:popupCharacters=":" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="\'"
+ latin:manualTemporaryUpperCaseCode="34"
+ latin:keyHintIcon="@drawable/key_hint_quote_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_quote_large_holo"
+ latin:popupCharacters=""" />
+ </default>
+ </switch>
+ <switch>
+ <case
+ latin:mode="email"
+ >
+ <Key
+ latin:keyLabel="_" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="-"
+ latin:manualTemporaryUpperCaseCode="95"
+ latin:keyHintIcon="@drawable/key_hint_underline_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_underline_large_holo"
+ latin:popupCharacters="_" />
+ </default>
+ </switch>
</default>
</switch>
<switch>
diff --git a/java/res/xml-xlarge/kbd_ru_rows.xml b/java/res/xml-xlarge/kbd_ru_rows.xml
index 008988a..c5cd043 100644
--- a/java/res/xml-xlarge/kbd_ru_rows.xml
+++ b/java/res/xml-xlarge/kbd_ru_rows.xml
@@ -105,11 +105,11 @@
latin:keyEdgeFlags="right" />
</Row>
<Row
- latin:keyWidth="8.042%p"
+ latin:keyWidth="7.520%p"
>
<Key
latin:keyStyle="shiftKeyStyle"
- latin:keyWidth="15.192%p"
+ latin:keyWidth="12.400%p"
latin:keyEdgeFlags="left" />
<Key
latin:keyLabel="я" />
@@ -131,8 +131,14 @@
<Key
latin:keyLabel="ю" />
<Key
+ latin:keyLabel="."
+ latin:manualTemporaryUpperCaseCode="44"
+ latin:keyHintIcon="@drawable/key_hint_comma_holo"
+ latin:manualTemporaryUpperCaseHintIcon="@drawable/key_hint_comma_large_holo"
+ latin:popupCharacters="," />
+ <Key
latin:keyStyle="shiftKeyStyle"
- latin:keyWidth="12.530%p"
+ latin:keyWidth="12.400%p"
latin:keyEdgeFlags="right" />
</Row>
<include
diff --git a/java/res/xml-xlarge/kbd_symbols.xml b/java/res/xml-xlarge/kbd_symbols.xml
index e56cc92..1061178 100644
--- a/java/res/xml-xlarge/kbd_symbols.xml
+++ b/java/res/xml-xlarge/kbd_symbols.xml
@@ -30,6 +30,8 @@
>
<include
latin:keyboardLayout="@xml/kbd_key_styles" />
+ <include
+ latin:keyboardLayout="@xml/kbd_currency_key_styles" />
<!-- This row is intentionally not marked as a top row -->
<Row
latin:keyWidth="8.272%p"
@@ -82,8 +84,7 @@
<Key
latin:keyLabel="#" />
<Key
- latin:keyLabel="$"
- latin:popupCharacters="¢,£,€,¥,₣,₤,₱" />
+ latin:keyStyle="currencyKeyStyle" />
<Key
latin:keyLabel="%"
latin:popupCharacters="‰" />
@@ -125,20 +126,53 @@
<Key
latin:keyLabel="="
latin:popupCharacters="≠,≈" />
- <Key
- latin:keyLabel=":" />
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
+ <Key
+ latin:keyLabel=":" />
+ </case>
+ <case
+ latin:mode="url"
+ >
+ <Key
+ latin:keyLabel="\'" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel=":" />
+ </default>
+ </switch>
<Key
latin:keyLabel=";" />
- <Key
- latin:keyLabel="," />
- <Key
- latin:keyLabel="." />
- <Key
- latin:keyLabel="!"
- latin:popupCharacters="¡" />
- <Key
- latin:keyLabel="\?"
- latin:popupCharacters="¿" />
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
+ <Key
+ latin:keyLabel="\'" />
+ <Key
+ latin:keyLabel="""
+ latin:popupCharacters="“,”,«,»,˝" />
+ <Key
+ latin:keyLabel="." />
+ <Key
+ latin:keyLabel="," />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="," />
+ <Key
+ latin:keyLabel="." />
+ <Key
+ latin:keyLabel="!"
+ latin:popupCharacters="¡" />
+ <Key
+ latin:keyLabel="\?"
+ latin:popupCharacters="¿" />
+ </default>
+ </switch>
<Key
latin:keyStyle="moreKeyStyle"
latin:keyWidth="12.530%p"
@@ -150,8 +184,16 @@
>
<Spacer
latin:horizontalGap="8.362%p" />
- <Key
- latin:keyStyle="settingsKeyStyle" />
+ <switch>
+ <case latin:hasSettingsKey="true">
+ <Key
+ latin:keyStyle="settingsKeyStyle" />
+ </case>
+ <default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
<Key
latin:keyLabel="/" />
<Key
@@ -159,11 +201,23 @@
<Key
latin:keyStyle="spaceKeyStyle"
latin:keyWidth="37.454%p" />
- <Key
- latin:keyLabel="""
- latin:popupCharacters="“,”,«,»,˝" />
- <Key
- latin:keyLabel="_" />
+ <switch>
+ <case
+ latin:languageCode="ru"
+ >
+ <Key
+ latin:keyLabel="_" />
+ <Key
+ latin:keyLabel="-" />
+ </case>
+ <default>
+ <Key
+ latin:keyLabel="""
+ latin:popupCharacters="“,”,«,»,˝" />
+ <Key
+ latin:keyLabel="_" />
+ </default>
+ </switch>
<switch>
<case
latin:voiceKeyEnabled="true"
diff --git a/java/res/xml-xlarge/kbd_symbols_shift.xml b/java/res/xml-xlarge/kbd_symbols_shift.xml
index f7cf24a..8359b75 100644
--- a/java/res/xml-xlarge/kbd_symbols_shift.xml
+++ b/java/res/xml-xlarge/kbd_symbols_shift.xml
@@ -46,21 +46,28 @@
<Key
latin:keyLabel="|" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="•"
latin:popupCharacters="♪,♥,♠,♦,♣" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="√" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="π"
latin:popupCharacters="Π" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="÷" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="×" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="§"
latin:popupCharacters="¶" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="Δ" />
<Key
latin:keyStyle="deleteKeyStyle"
@@ -76,19 +83,25 @@
latin:keyWidth="11.167%p"
latin:keyEdgeFlags="left" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="£" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¢" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="€" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¥" />
<Key
latin:keyLabel="^"
latin:popupCharacters="↑,↓,←,→" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="°" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="±"
latin:popupCharacters="∞" />
<Key
@@ -110,20 +123,26 @@
<Key
latin:keyLabel="\\" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="©" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="®" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="™" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="℅" />
<Key
latin:keyLabel="[" />
<Key
latin:keyLabel="]" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¡" />
<Key
+ latin:keyStyle="nonPasswordSymbolKeyStyle"
latin:keyLabel="¿" />
<Key
latin:keyStyle="moreKeyStyle"
@@ -136,8 +155,16 @@
>
<Spacer
latin:horizontalGap="24.446%p" />
- <Key
- latin:keyStyle="settingsKeyStyle" />
+ <switch>
+ <case latin:hasSettingsKey="true">
+ <Key
+ latin:keyStyle="settingsKeyStyle" />
+ </case>
+ <default>
+ <Spacer
+ latin:horizontalGap="8.042%p" />
+ </default>
+ </switch>
<Key
latin:keyStyle="spaceKeyStyle"
latin:keyWidth="37.454%p" />
diff --git a/java/res/xml/kbd_currency_key_styles.xml b/java/res/xml/kbd_currency_key_styles.xml
new file mode 100644
index 0000000..b30dd64
--- /dev/null
+++ b/java/res/xml/kbd_currency_key_styles.xml
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<merge
+ xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+ <switch>
+ <case
+ latin:passwordInput="true"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="$"
+ latin:popupCharacters="@string/alternates_for_currency_dollar" />
+ </case>
+ <!-- Countries using Euro currency, 23 countries as for January 2011. -->
+ <!-- 1. Andorra (ca_AD, ca_ES) -->
+ <case
+ latin:languageCode="ca"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 2. Austria (de_AT) -->
+<!-- <case-->
+<!-- latin:countryCode="AT"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 3. Belgium (nl_BE, fr_BE, de_BE) -->
+<!-- <case-->
+<!-- latin:countryCode="BE"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 4. Cyprus (el_CY, tr_CY) -->
+ <case
+ latin:countryCode="CY"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 5. Estonia (et_EE) -->
+<!-- <case-->
+<!-- latin:languageCode="et"-->
+<!-- latin:countryCode=""-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 6. Finland (fi_FI, sv_FI) -->
+ <case
+ latin:languageCode="fi"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 7. France (fr_FR) -->
+ <case
+ latin:languageCode="fr"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 8. Germany (de_DE) -->
+ <case
+ latin:languageCode="de"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 9. Greece (el_GR) -->
+ <case
+ latin:languageCode="el"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 10. Ireland (ga_IE, en_IE) -->
+ <case
+ latin:countryCode="IE"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 11. Italy (it_IT) -->
+ <case
+ latin:languageCode="it"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 12. Kosovo -->
+<!-- <case-->
+<!-- latin:countryCode="XK"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 13. Luxembourg (lb_LU, fr_LU, de_LU) -->
+ <case
+ latin:countryCode="LU"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 14. Malta (mt_MT, en_MT) -->
+ <case
+ latin:countryCode="MT"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 15. Monaco (fr_MO) -->
+<!-- <case-->
+<!-- latin:countryCode="MO"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 16. Montenegro (sla_ME) -->
+ <case
+ latin:countryCode="ME"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 17. Netherlands (nl_NL) -->
+ <case
+ latin:languageCode="nl"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 18. Portugal (pt_PT) -->
+ <case
+ latin:languageCode="pt"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 19. San Marino (it_SM) -->
+<!-- <case-->
+<!-- latin:countryCode="SM"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- 20. Slovakia (sk_SK) -->
+ <case
+ latin:languageCode="sk"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 21. Slovenia (sl_SI) -->
+ <case
+ latin:languageCode="sl"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 22. Spain (es_ES, ca_ES) -->
+ <case
+ latin:languageCode="es"
+ latin:countryCode=""
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="€"
+ latin:popupCharacters="@string/alternates_for_currency_euro" />
+ </case>
+ <!-- 23. Vatican City (it_VA) -->
+<!-- <case-->
+<!-- latin:countryCode="VA"-->
+<!-- >-->
+<!-- <key-style-->
+<!-- latin:styleName="currencyKeyStyle"-->
+<!-- latin:keyLabel="€"-->
+<!-- latin:popupCharacters="@string/alternates_for_currency_euro" />-->
+<!-- </case>-->
+ <!-- United Kingdom -->
+ <case
+ latin:countryCode="GB"
+ >
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="£"
+ latin:popupCharacters="@string/alternates_for_currency_pound" />
+ </case>
+ <default>
+ <key-style
+ latin:styleName="currencyKeyStyle"
+ latin:keyLabel="$"
+ latin:popupCharacters="@string/alternates_for_currency_dollar" />
+ </default>
+ </switch>
+</merge>
\ No newline at end of file
diff --git a/java/res/xml/kbd_key_styles.xml b/java/res/xml/kbd_key_styles.xml
index 3b35f35..473510e 100644
--- a/java/res/xml/kbd_key_styles.xml
+++ b/java/res/xml/kbd_key_styles.xml
@@ -182,7 +182,7 @@
<!-- Return key style -->
<switch>
<case
- latin:imeOptions="actionGo"
+ latin:imeAction="actionGo"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -191,7 +191,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionNext"
+ latin:imeAction="actionNext"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -200,7 +200,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionDone"
+ latin:imeAction="actionDone"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -209,7 +209,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionSend"
+ latin:imeAction="actionSend"
>
<key-style
latin:styleName="returnKeyStyle"
@@ -218,7 +218,7 @@
latin:parentStyle="functionalKeyStyle" />
</case>
<case
- latin:imeOptions="actionSearch"
+ latin:imeAction="actionSearch"
>
<switch>
<case
diff --git a/java/res/xml/kbd_number.xml b/java/res/xml/kbd_number.xml
index f4fe840..7bd679b 100644
--- a/java/res/xml/kbd_number.xml
+++ b/java/res/xml/kbd_number.xml
@@ -31,6 +31,7 @@
>
<include
latin:keyboardLayout="@xml/kbd_key_styles" />
+ <!-- TODO: Should add number password layout just like the xlarge layout does. -->
<switch>
<case
latin:colorScheme="white"
diff --git a/java/res/xml/kbd_symbols.xml b/java/res/xml/kbd_symbols.xml
index 5d62dea..b3b3f4e 100644
--- a/java/res/xml/kbd_symbols.xml
+++ b/java/res/xml/kbd_symbols.xml
@@ -31,6 +31,8 @@
>
<include
latin:keyboardLayout="@xml/kbd_key_styles" />
+ <include
+ latin:keyboardLayout="@xml/kbd_currency_key_styles" />
<Row
latin:rowEdgeFlags="top"
>
@@ -71,8 +73,7 @@
<Key
latin:keyLabel="\#" />
<Key
- latin:keyLabel="$"
- latin:popupCharacters="¢,£,€,¥,₣,₤,₱" />
+ latin:keyStyle="currencyKeyStyle" />
<Key
latin:keyLabel="%"
latin:popupCharacters="‰" />
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index b1f7379..8dec7ab 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -65,6 +65,7 @@
android:label="@string/subtype_mode_de_keyboard"
android:imeSubtypeLocale="de"
android:imeSubtypeMode="keyboard"
+ android:imeSubtypeExtraValue="requiresGermanUmlautProcessing"
/>
<subtype android:icon="@drawable/ic_subtype_mic"
android:label="@string/subtype_mode_de_voice"
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 9ea801e..d031415 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -38,6 +38,7 @@
<CheckBoxPreference
android:key="sound_on"
android:title="@string/sound_on_keypress"
+ android:defaultValue="@bool/config_default_sound_enabled"
android:persistent="true"
/>
@@ -53,7 +54,7 @@
android:title="@string/prefs_enable_recorrection"
android:summary="@string/prefs_enable_recorrection_summary"
android:persistent="true"
- android:defaultValue="@bool/default_recorrection_enabled"
+ android:defaultValue="@bool/config_default_recorrection_enabled"
/>
<ListPreference
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 23886ad..7396f05 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -95,7 +95,7 @@
public boolean mPressed;
/** If this is a sticky key, is it on? */
public boolean mOn;
- /** Key is enabled or not. */
+ /** Key is enabled and responds on press */
public boolean mEnabled = true;
private final static int[] KEY_STATE_NORMAL_ON = {
@@ -226,6 +226,7 @@
mRepeatable = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable, false);
mModifier = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier, false);
mSticky = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky, false);
+ mEnabled = style.getBoolean(keyAttr, R.styleable.Keyboard_Key_enabled, true);
mEdgeFlags = style.getFlag(keyAttr, R.styleable.Keyboard_Key_keyEdgeFlags, 0)
| row.mRowEdgeFlags;
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index e7a9d85..1a4f901 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -17,10 +17,12 @@
package com.android.inputmethod.keyboard;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
public abstract class KeyDetector {
public static final int NOT_A_KEY = -1;
+ public static final int NOT_A_CODE = -1;
protected Keyboard mKeyboard;
@@ -104,8 +106,35 @@
*
* @param x The x-coordinate of a touch point
* @param y The y-coordinate of a touch point
- * @param allKeys All nearby key indices are returned in this array
+ * @param allCodes All nearby key code except functional key are returned in this array
* @return The nearest key index
*/
- abstract public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys);
+ abstract public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes);
+
+ /**
+ * Compute the most common key width in order to use it as proximity key detection threshold.
+ *
+ * @param keyboard The keyboard to compute the most common key width
+ * @return The most common key width in the keyboard
+ */
+ public static int getMostCommonKeyWidth(final Keyboard keyboard) {
+ if (keyboard == null) return 0;
+ final List<Key> keys = keyboard.getKeys();
+ if (keys == null || keys.size() == 0) return 0;
+ final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
+ int maxCount = 0;
+ int mostCommonWidth = 0;
+ for (final Key key : keys) {
+ final Integer width = key.mWidth + key.mGap;
+ Integer count = histogram.get(width);
+ if (count == null)
+ count = 0;
+ histogram.put(width, ++count);
+ if (count > maxCount) {
+ maxCount = count;
+ mostCommonWidth = width;
+ }
+ }
+ return mostCommonWidth;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyStyles.java b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
index 44ec531..169f2e6 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyStyles.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyStyles.java
@@ -188,6 +188,7 @@
readBoolean(keyAttr, R.styleable.Keyboard_Key_isModifier);
readBoolean(keyAttr, R.styleable.Keyboard_Key_isSticky);
readBoolean(keyAttr, R.styleable.Keyboard_Key_isRepeatable);
+ readBoolean(keyAttr, R.styleable.Keyboard_Key_enabled);
}
private void readDrawable(TypedArray a, int index) {
diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java
index 3a0bf53..06d4468 100644
--- a/java/src/com/android/inputmethod/keyboard/Keyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java
@@ -132,6 +132,7 @@
// Variables for pre-computing nearest keys.
+ // TODO: Change GRID_WIDTH and GRID_HEIGHT to private.
public final int GRID_WIDTH;
public final int GRID_HEIGHT;
private final int GRID_SIZE;
@@ -143,6 +144,8 @@
/** Number of key widths from current touch point to search for nearest keys. */
private static float SEARCH_DISTANCE = 1.2f;
+ private final ProximityInfo mProximityInfo;
+
/**
* Creates a keyboard from the given xml key layout file.
* @param context the application or service context
@@ -171,6 +174,11 @@
mDefaultHeight = mDefaultWidth;
mId = id;
loadKeyboard(context, xmlLayoutResId);
+ mProximityInfo = new ProximityInfo(GRID_WIDTH, GRID_HEIGHT);
+ }
+
+ public int getProximityInfo() {
+ return mProximityInfo.getNativeProximityInfo(this);
}
public List<Key> getKeys() {
@@ -345,7 +353,8 @@
return mId != null && mId.isNumberKeyboard();
}
- private void computeNearestNeighbors() {
+ // TODO: Move this function to ProximityInfo and make this private.
+ public void computeNearestNeighbors() {
// Round-up so we don't have any pixels outside the grid
mCellWidth = (getMinWidth() + GRID_WIDTH - 1) / GRID_WIDTH;
mCellHeight = (getHeight() + GRID_HEIGHT - 1) / GRID_HEIGHT;
@@ -369,6 +378,7 @@
mGridNeighbors[(y / mCellHeight) * GRID_WIDTH + (x / mCellWidth)] = cell;
}
}
+ mProximityInfo.setProximityInfo(mGridNeighbors, getMinWidth(), getHeight(), mKeys);
}
public boolean isInside(Key key, int x, int y) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 734a55a..098af21 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -24,16 +24,20 @@
*
* @param primaryCode the unicode of the key being pressed. If the touch is not on a valid key,
* the value will be zero.
+ * @param withSliding true if pressing has occurred because the user slid finger from other key
+ * to this key without releasing the finger.
*/
- public void onPress(int primaryCode);
+ public void onPress(int primaryCode, boolean withSliding);
/**
* Called when the user releases a key. This is sent after the {@link #onCodeInput} is called.
* For keys that repeat, this is only called once.
*
* @param primaryCode the code of the key that was released
+ * @param withSliding true if releasing has occurred because the user slid finger from the key
+ * to other key without releasing the finger.
*/
- public void onRelease(int primaryCode);
+ public void onRelease(int primaryCode, boolean withSliding);
/**
* Send a key code to the listener.
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index db86740..d09f678 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard;
import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.Utils;
import android.view.inputmethod.EditorInfo;
@@ -41,29 +42,35 @@
public final int mMode;
public final int mXmlId;
public final int mColorScheme;
+ public final boolean mPasswordInput;
public final boolean mHasSettingsKey;
public final boolean mVoiceKeyEnabled;
public final boolean mHasVoiceKey;
- public final int mImeOptions;
+ public final int mImeAction;
public final boolean mEnableShiftLock;
public final String mXmlName;
private final int mHashCode;
- public KeyboardId(String xmlName, int xmlId, Locale locale, int orientation, int mode,
- int colorScheme, boolean hasSettingsKey, boolean voiceKeyEnabled, boolean hasVoiceKey,
- int imeOptions, boolean enableShiftLock) {
+ public KeyboardId(String xmlName, int xmlId, int colorScheme, Locale locale, int orientation,
+ int mode, EditorInfo attribute, boolean hasSettingsKey, boolean voiceKeyEnabled,
+ boolean hasVoiceKey, boolean enableShiftLock) {
+ final int inputType = (attribute != null) ? attribute.inputType : 0;
+ final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
this.mLocale = locale;
this.mOrientation = orientation;
this.mMode = mode;
this.mXmlId = xmlId;
this.mColorScheme = colorScheme;
+ this.mPasswordInput = Utils.isPasswordInputType(inputType)
+ || Utils.isVisiblePasswordInputType(inputType);
this.mHasSettingsKey = hasSettingsKey;
this.mVoiceKeyEnabled = voiceKeyEnabled;
this.mHasVoiceKey = hasVoiceKey;
- // We are interested only in IME_MASK_ACTION enum value and IME_FLAG_NO_ENTER_ACTION.
- this.mImeOptions = imeOptions
- & (EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
+ // We are interested only in {@link EditorInfo#IME_MASK_ACTION} enum value and
+ // {@link EditorInfo#IME_FLAG_NO_ENTER_ACTION}.
+ this.mImeAction = imeOptions & (
+ EditorInfo.IME_MASK_ACTION | EditorInfo.IME_FLAG_NO_ENTER_ACTION);
this.mEnableShiftLock = enableShiftLock;
this.mXmlName = xmlName;
@@ -73,10 +80,11 @@
mode,
xmlId,
colorScheme,
+ mPasswordInput,
hasSettingsKey,
voiceKeyEnabled,
hasVoiceKey,
- imeOptions,
+ mImeAction,
enableShiftLock,
});
}
@@ -112,10 +120,11 @@
&& other.mMode == this.mMode
&& other.mXmlId == this.mXmlId
&& other.mColorScheme == this.mColorScheme
+ && other.mPasswordInput == this.mPasswordInput
&& other.mHasSettingsKey == this.mHasSettingsKey
&& other.mVoiceKeyEnabled == this.mVoiceKeyEnabled
&& other.mHasVoiceKey == this.mHasVoiceKey
- && other.mImeOptions == this.mImeOptions
+ && other.mImeAction == this.mImeAction
&& other.mEnableShiftLock == this.mEnableShiftLock;
}
@@ -126,17 +135,19 @@
@Override
public String toString() {
- return String.format("[%s.xml %s %s %s imeOptions=%s %s%s%s%s%s]",
+ return String.format("[%s.xml %s %s %s imeAction=%s %s%s%s%s%s%s]",
mXmlName,
mLocale,
(mOrientation == 1 ? "port" : "land"),
modeName(mMode),
- imeOptionsName(mImeOptions),
- colorSchemeName(mColorScheme),
+ imeOptionsName(mImeAction),
+ (mPasswordInput ? " passwordInput" : ""),
(mHasSettingsKey ? " hasSettingsKey" : ""),
(mVoiceKeyEnabled ? " voiceKeyEnabled" : ""),
(mHasVoiceKey ? " hasVoiceKey" : ""),
- (mEnableShiftLock ? " enableShiftLock" : ""));
+ (mEnableShiftLock ? " enableShiftLock" : ""),
+ colorSchemeName(mColorScheme)
+ );
}
public static String modeName(int mode) {
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
index e8324e5..feb56ab 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardParser.java
@@ -103,7 +103,7 @@
*/
public class KeyboardParser {
- private static final String TAG = "KeyboardParser";
+ private static final String TAG = KeyboardParser.class.getSimpleName();
private static final boolean DEBUG = false;
// Keyboard XML Tags
@@ -279,8 +279,8 @@
checkEndTag(TAG_KEY, parser);
} else {
Key key = new Key(mResources, row, mCurrentX, mCurrentY, parser, mKeyStyles);
- if (DEBUG) Log.d(TAG, String.format("<%s keyLabel=%s code=%d popupCharacters=%s />",
- TAG_KEY, key.mLabel, key.mCode,
+ if (DEBUG) Log.d(TAG, String.format("<%s%s keyLabel=%s code=%d popupCharacters=%s />",
+ TAG_KEY, (key.mEnabled ? "" : " disabled"), key.mLabel, key.mCode,
Arrays.toString(key.mPopupCharacters)));
checkEndTag(TAG_KEY, parser);
keys.add(key);
@@ -419,6 +419,8 @@
try {
final boolean modeMatched = matchInteger(a,
R.styleable.Keyboard_Case_mode, id.mMode);
+ final boolean passwordInputMatched = matchBoolean(a,
+ R.styleable.Keyboard_Case_passwordInput, id.mPasswordInput);
final boolean settingsKeyMatched = matchBoolean(a,
R.styleable.Keyboard_Case_hasSettingsKey, id.mHasSettingsKey);
final boolean voiceEnabledMatched = matchBoolean(a,
@@ -427,24 +429,34 @@
R.styleable.Keyboard_Case_hasVoiceKey, id.mHasVoiceKey);
final boolean colorSchemeMatched = matchInteger(viewAttr,
R.styleable.KeyboardView_colorScheme, id.mColorScheme);
- // As noted at KeyboardSwitcher.KeyboardId class, we are interested only in
- // enum value masked by IME_MASK_ACTION and IME_FLAG_NO_ENTER_ACTION. So matching
+ // As noted at {@link KeyboardId} class, we are interested only in enum value masked by
+ // {@link android.view.inputmethod.EditorInfo#IME_MASK_ACTION} and
+ // {@link android.view.inputmethod.EditorInfo#IME_FLAG_NO_ENTER_ACTION}. So matching
// this attribute with id.mImeOptions as integer value is enough for our purpose.
- final boolean imeOptionsMatched = matchInteger(a,
- R.styleable.Keyboard_Case_imeOptions, id.mImeOptions);
- final boolean selected = modeMatched && settingsKeyMatched && voiceEnabledMatched
- && voiceKeyMatched && colorSchemeMatched && imeOptionsMatched;
+ final boolean imeActionMatched = matchInteger(a,
+ R.styleable.Keyboard_Case_imeAction, id.mImeAction);
+ final boolean languageCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_languageCode, id.mLocale.getLanguage());
+ final boolean countryCodeMatched = matchString(a,
+ R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry());
+ final boolean selected = modeMatched && passwordInputMatched && settingsKeyMatched
+ && voiceEnabledMatched && voiceKeyMatched && colorSchemeMatched
+ && imeActionMatched && languageCodeMatched && countryCodeMatched;
- if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s> %s", TAG_CASE,
+ if (DEBUG) Log.d(TAG, String.format("<%s%s%s%s%s%s%s%s%s%s> %s", TAG_CASE,
textAttr(KeyboardId.modeName(
a.getInt(R.styleable.Keyboard_Case_mode, -1)), "mode"),
textAttr(KeyboardId.colorSchemeName(
- a.getInt(R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"),
+ viewAttr.getInt(
+ R.styleable.KeyboardView_colorScheme, -1)), "colorSchemeName"),
+ booleanAttr(a, R.styleable.Keyboard_Case_passwordInput, "passwordInput"),
booleanAttr(a, R.styleable.Keyboard_Case_hasSettingsKey, "hasSettingsKey"),
booleanAttr(a, R.styleable.Keyboard_Case_voiceKeyEnabled, "voiceKeyEnabled"),
booleanAttr(a, R.styleable.Keyboard_Case_hasVoiceKey, "hasVoiceKey"),
textAttr(KeyboardId.imeOptionsName(
- a.getInt(R.styleable.Keyboard_Case_imeOptions, -1)), "imeOptions"),
+ a.getInt(R.styleable.Keyboard_Case_imeAction, -1)), "imeAction"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_languageCode), "languageCode"),
+ textAttr(a.getString(R.styleable.Keyboard_Case_countryCode), "countryCode"),
Boolean.toString(selected)));
return selected;
@@ -466,6 +478,12 @@
return !a.hasValue(index) || a.getBoolean(index, false) == value;
}
+ private static boolean matchString(TypedArray a, int index, String value) {
+ // If <case> does not have "index" attribute, that means this <case> is wild-card for the
+ // attribute.
+ return !a.hasValue(index) || a.getString(index).equals(value);
+ }
+
private boolean parseDefault(XmlResourceParser parser, Row row, List<Key> keys)
throws XmlPullParserException, IOException {
if (DEBUG) Log.d(TAG, String.format("<%s>", TAG_DEFAULT));
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 2648ff3..64a23ab 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -28,6 +28,7 @@
import android.content.res.Resources;
import android.util.Log;
import android.view.InflateException;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import java.lang.ref.SoftReference;
@@ -66,8 +67,7 @@
private final HashMap<KeyboardId, SoftReference<LatinKeyboard>> mKeyboardCache =
new HashMap<KeyboardId, SoftReference<LatinKeyboard>>();
- private int mMode = KeyboardId.MODE_TEXT; /* default value */
- private int mImeOptions;
+ private EditorInfo mAttribute;
private boolean mIsSymbols;
/** mIsAutoCorrectionActive indicates that auto corrected word will be input instead of
* what user actually typed. */
@@ -83,8 +83,8 @@
private static final int AUTO_MODE_SWITCH_STATE_CHORDING = 4;
private int mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
- // Indicates whether or not we have the settings key
- private boolean mHasSettingsKey;
+ // Indicates whether or not we have the settings key in option of settings
+ private boolean mSettingsKeyEnabledInSettings;
private static final int SETTINGS_KEY_MODE_AUTO = R.string.settings_key_mode_auto;
private static final int SETTINGS_KEY_MODE_ALWAYS_SHOW =
R.string.settings_key_mode_always_show;
@@ -122,77 +122,47 @@
prefs.registerOnSharedPreferenceChangeListener(sInstance);
}
- private void makeSymbolsKeyboardIds() {
- final Locale locale = mSubtypeSwitcher.getInputLocale();
- final Resources res = mInputMethodService.getResources();
- final int orientation = res.getConfiguration().orientation;
- final int mode = mMode;
- final int colorScheme = getColorScheme();
- final boolean hasSettingsKey = mHasSettingsKey;
- final boolean voiceKeyEnabled = mVoiceKeyEnabled;
- final boolean hasVoiceKey = voiceKeyEnabled && !mVoiceButtonOnPrimary;
- final int imeOptions = mImeOptions;
- // Note: This comment is only applied for phone number keyboard layout.
- // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch
- // between "phone keyboard" and "phone symbols keyboard". But on xlarge device,
- // "@integer/key_shift" key code is used for that purpose in order to properly display
- // "more" and "locked more" key labels. To achieve these behavior, we should initialize
- // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard"
- // respectively here for xlarge device's layout switching.
- int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
- mSymbolsId = new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
- hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
- xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
- mSymbolsShiftedId = new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, colorScheme,
- hasSettingsKey, voiceKeyEnabled, hasVoiceKey, imeOptions, true);
- }
-
- private boolean hasVoiceKey(boolean isSymbols) {
- return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary);
- }
-
- public void loadKeyboard(int mode, int imeOptions, boolean voiceKeyEnabled,
+ public void loadKeyboard(EditorInfo attribute, boolean voiceKeyEnabled,
boolean voiceButtonOnPrimary) {
mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_ALPHA;
try {
- if (mInputView == null) return;
- final Keyboard oldKeyboard = mInputView.getKeyboard();
- loadKeyboardInternal(mode, imeOptions, voiceKeyEnabled, voiceButtonOnPrimary, false);
- final Keyboard newKeyboard = mInputView.getKeyboard();
- if (newKeyboard.isAlphaKeyboard()) {
- final boolean localeChanged = (oldKeyboard == null)
- || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
- mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
- }
+ loadKeyboardInternal(attribute, voiceKeyEnabled, voiceButtonOnPrimary, false);
} catch (RuntimeException e) {
- Log.w(TAG, e);
- LatinImeLogger.logOnException(mode + "," + imeOptions, e);
+ // Get KeyboardId to record which keyboard has been failed to load.
+ final KeyboardId id = getKeyboardId(attribute, false);
+ Log.w(TAG, "loading keyboard failed: " + id, e);
+ LatinImeLogger.logOnException(id.toString(), e);
}
}
- private void loadKeyboardInternal(int mode, int imeOptions, boolean voiceButtonEnabled,
+ private void loadKeyboardInternal(EditorInfo attribute, boolean voiceButtonEnabled,
boolean voiceButtonOnPrimary, boolean isSymbols) {
if (mInputView == null) return;
- mMode = mode;
- mImeOptions = imeOptions;
+ mAttribute = attribute;
mVoiceKeyEnabled = voiceButtonEnabled;
mVoiceButtonOnPrimary = voiceButtonOnPrimary;
mIsSymbols = isSymbols;
// Update the settings key state because number of enabled IMEs could have been changed
- mHasSettingsKey = getSettingsKeyMode(mPrefs, mInputMethodService);
- final KeyboardId id = getKeyboardId(mode, imeOptions, isSymbols);
+ mSettingsKeyEnabledInSettings = getSettingsKeyMode(mPrefs, mInputMethodService);
+ final KeyboardId id = getKeyboardId(attribute, isSymbols);
final Keyboard oldKeyboard = mInputView.getKeyboard();
if (oldKeyboard != null && oldKeyboard.mId.equals(id))
return;
- makeSymbolsKeyboardIds();
+ makeSymbolsKeyboardIds(id.mMode, attribute);
mCurrentId = id;
mInputView.setPreviewEnabled(mInputMethodService.getPopupOn());
- mInputView.setKeyboard(getKeyboard(id));
+ setKeyboard(getKeyboard(id));
+ }
+
+ private void setKeyboard(final Keyboard newKeyboard) {
+ final Keyboard oldKeyboard = mInputView.getKeyboard();
+ mInputView.setKeyboard(newKeyboard);
+ final boolean localeChanged = (oldKeyboard == null)
+ || !newKeyboard.mId.mLocale.equals(oldKeyboard.mId.mLocale);
+ mInputMethodService.mHandler.startDisplayLanguageOnSpacebar(localeChanged);
}
private LatinKeyboard getKeyboard(KeyboardId id) {
@@ -224,11 +194,22 @@
// displayed on its spacebar, it might have had arbitrary text fade factor. In such case,
// we should reset the text fade factor. It is also applicable to shortcut key.
keyboard.setSpacebarTextFadeFactor(0.0f, null);
- keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutAvailable(), null);
+ keyboard.updateShortcutKey(mSubtypeSwitcher.isShortcutImeReady(), null);
return keyboard;
}
- private KeyboardId getKeyboardId(int mode, int imeOptions, boolean isSymbols) {
+ private boolean hasVoiceKey(boolean isSymbols) {
+ return mVoiceKeyEnabled && (isSymbols != mVoiceButtonOnPrimary);
+ }
+
+ private boolean hasSettingsKey(EditorInfo attribute) {
+ return mSettingsKeyEnabledInSettings
+ && !Utils.inPrivateImeOptions(mInputMethodService.getPackageName(),
+ LatinIME.IME_OPTION_NO_SETTINGS_KEY, attribute);
+ }
+
+ private KeyboardId getKeyboardId(EditorInfo attribute, boolean isSymbols) {
+ final int mode = Utils.getKeyboardMode(attribute);
final boolean hasVoiceKey = hasVoiceKey(isSymbols);
final int charColorId = getColorScheme();
final int xmlId;
@@ -256,16 +237,40 @@
enableShiftLock = true;
}
}
+ final boolean hasSettingsKey = hasSettingsKey(attribute);
final Resources res = mInputMethodService.getResources();
final int orientation = res.getConfiguration().orientation;
final Locale locale = mSubtypeSwitcher.getInputLocale();
return new KeyboardId(
- res.getResourceEntryName(xmlId), xmlId, locale, orientation, mode, charColorId,
- mHasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, imeOptions, enableShiftLock);
+ res.getResourceEntryName(xmlId), xmlId, charColorId, locale, orientation, mode,
+ attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, enableShiftLock);
+ }
+
+ private void makeSymbolsKeyboardIds(final int mode, EditorInfo attribute) {
+ final Locale locale = mSubtypeSwitcher.getInputLocale();
+ final Resources res = mInputMethodService.getResources();
+ final int orientation = res.getConfiguration().orientation;
+ final int colorScheme = getColorScheme();
+ final boolean hasVoiceKey = mVoiceKeyEnabled && !mVoiceButtonOnPrimary;
+ final boolean hasSettingsKey = hasSettingsKey(attribute);
+ // Note: This comment is only applied for phone number keyboard layout.
+ // On non-xlarge device, "@integer/key_switch_alpha_symbol" key code is used to switch
+ // between "phone keyboard" and "phone symbols keyboard". But on xlarge device,
+ // "@integer/key_shift" key code is used for that purpose in order to properly display
+ // "more" and "locked more" key labels. To achieve these behavior, we should initialize
+ // mSymbolsId and mSymbolsShiftedId to "phone keyboard" and "phone symbols keyboard"
+ // respectively here for xlarge device's layout switching.
+ int xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone : R.xml.kbd_symbols;
+ final String xmlName = res.getResourceEntryName(xmlId);
+ mSymbolsId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
+ attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
+ xmlId = mode == KeyboardId.MODE_PHONE ? R.xml.kbd_phone_symbols : R.xml.kbd_symbols_shift;
+ mSymbolsShiftedId = new KeyboardId(xmlName, xmlId, colorScheme, locale, orientation, mode,
+ attribute, hasSettingsKey, mVoiceKeyEnabled, hasVoiceKey, true);
}
public int getKeyboardMode() {
- return mMode;
+ return mCurrentId != null ? mCurrentId.mMode : KeyboardId.MODE_TEXT;
}
public boolean isAlphabetMode() {
@@ -278,22 +283,19 @@
public boolean isKeyboardAvailable() {
if (mInputView != null)
- return mInputView.getLatinKeyboard() != null;
+ return mInputView.getKeyboard() != null;
return false;
}
- private LatinKeyboard getLatinKeyboard() {
- if (mInputView != null)
- return mInputView.getLatinKeyboard();
+ public LatinKeyboard getLatinKeyboard() {
+ if (mInputView != null) {
+ final Keyboard keyboard = mInputView.getKeyboard();
+ if (keyboard instanceof LatinKeyboard)
+ return (LatinKeyboard)keyboard;
+ }
return null;
}
- public void setPreferredLetters(int[] frequencies) {
- LatinKeyboard latinKeyboard = getLatinKeyboard();
- if (latinKeyboard != null)
- latinKeyboard.setPreferredLetters(frequencies);
- }
-
public void keyReleased() {
LatinKeyboard latinKeyboard = getLatinKeyboard();
if (latinKeyboard != null)
@@ -342,7 +344,8 @@
// state when shift key is pressed to go to normal mode.
// On the other hand, on distinct multi touch panel device, turning off the shift locked
// state with shift key pressing is handled by onReleaseShift().
- if (!hasDistinctMultitouch() && !shifted && latinKeyboard.isShiftLocked()) {
+ if ((!hasDistinctMultitouch() || isAccessibilityEnabled())
+ && !shifted && latinKeyboard.isShiftLocked()) {
latinKeyboard.setShiftLocked(false);
}
if (latinKeyboard.setShifted(shifted)) {
@@ -437,14 +440,17 @@
updateShiftState();
}
- public void onPressShift() {
+ public void onPressShift(boolean withSliding) {
if (!isKeyboardAvailable())
return;
+ // If accessibility is enabled, disable momentary shift lock.
+ if (isAccessibilityEnabled())
+ return;
ShiftKeyState shiftKeyState = mShiftKeyState;
if (DEBUG_STATE)
Log.d(TAG, "onPressShift:"
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
- + " shiftKeyState=" + shiftKeyState);
+ + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
if (isAlphabetMode()) {
if (isShiftLocked()) {
// Shift key is pressed while caps lock state, we will treat this state as shifted
@@ -472,25 +478,30 @@
}
}
- public void onReleaseShift() {
+ public void onReleaseShift(boolean withSliding) {
if (!isKeyboardAvailable())
return;
+ // If accessibility is enabled, disable momentary shift lock.
+ if (isAccessibilityEnabled())
+ return;
ShiftKeyState shiftKeyState = mShiftKeyState;
if (DEBUG_STATE)
Log.d(TAG, "onReleaseShift:"
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
- + " shiftKeyState=" + shiftKeyState);
+ + " shiftKeyState=" + shiftKeyState + " sliding=" + withSliding);
if (isAlphabetMode()) {
if (shiftKeyState.isMomentary()) {
// After chording input while normal state.
toggleShift();
- } else if (isShiftLocked() && !shiftKeyState.isIgnoring()) {
+ } else if (isShiftLocked() && !shiftKeyState.isIgnoring() && !withSliding) {
// Shift has been pressed without chording while caps lock state.
toggleCapsLock();
- } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()) {
+ } else if (isShiftedOrShiftLocked() && shiftKeyState.isPressingOnShifted()
+ && !withSliding) {
// Shift has been pressed without chording while shifted state.
toggleShift();
- } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()) {
+ } else if (isManualTemporaryUpperCaseFromAuto() && shiftKeyState.isPressing()
+ && !withSliding) {
// Shift has been pressed without chording while manual temporary upper case
// transited from automatic temporary upper case.
toggleShift();
@@ -500,6 +511,9 @@
}
public void onPressSymbol() {
+ // If accessibility is enabled, disable momentary symbol lock.
+ if (isAccessibilityEnabled())
+ return;
if (DEBUG_STATE)
Log.d(TAG, "onPressSymbol:"
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -510,6 +524,9 @@
}
public void onReleaseSymbol() {
+ // If accessibility is enabled, disable momentary symbol lock.
+ if (isAccessibilityEnabled())
+ return;
if (DEBUG_STATE)
Log.d(TAG, "onReleaseSymbol:"
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -522,6 +539,9 @@
}
public void onOtherKeyPressed() {
+ // If accessibility is enabled, disable momentary mode locking.
+ if (isAccessibilityEnabled())
+ return;
if (DEBUG_STATE)
Log.d(TAG, "onOtherKeyPressed:"
+ " keyboard=" + getLatinKeyboard().getKeyboardShiftState()
@@ -556,7 +576,7 @@
// indicator, we need to call enableShiftLock() and setShiftLocked(false).
keyboard.setShifted(false);
}
- mInputView.setKeyboard(keyboard);
+ setKeyboard(keyboard);
}
public boolean isInMomentaryAutoModeSwitchState() {
@@ -572,8 +592,7 @@
}
private void toggleKeyboardMode() {
- loadKeyboardInternal(mMode, mImeOptions, mVoiceKeyEnabled, mVoiceButtonOnPrimary,
- !mIsSymbols);
+ loadKeyboardInternal(mAttribute, mVoiceKeyEnabled, mVoiceButtonOnPrimary, !mIsSymbols);
if (mIsSymbols) {
mAutoModeSwitchState = AUTO_MODE_SWITCH_STATE_SYMBOL_BEGIN;
} else {
@@ -581,6 +600,10 @@
}
}
+ public boolean isAccessibilityEnabled() {
+ return mInputView != null && mInputView.isAccessibilityEnabled();
+ }
+
public boolean hasDistinctMultitouch() {
return mInputView != null && mInputView.hasDistinctMultitouch();
}
@@ -696,7 +719,8 @@
createInputViewInternal(layoutId, false);
postSetInputView();
} else if (Settings.PREF_SETTINGS_KEY.equals(key)) {
- mHasSettingsKey = getSettingsKeyMode(sharedPreferences, mInputMethodService);
+ mSettingsKeyEnabledInSettings = getSettingsKeyMode(sharedPreferences,
+ mInputMethodService);
createInputViewInternal(mLayoutId, true);
postSetInputView();
}
@@ -732,7 +756,9 @@
Context.INPUT_METHOD_SERVICE))))) {
return true;
}
+ return false;
}
- return false;
+ // If the show settings key option is disabled, we always try showing the settings key.
+ return true;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 19f1fa8..61af15b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -26,6 +26,7 @@
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PorterDuff;
@@ -36,6 +37,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
@@ -68,8 +70,7 @@
* @attr ref R.styleable#KeyboardView_popupLayout
*/
public class KeyboardView extends View implements PointerTracker.UIProxy {
- private static final String TAG = "KeyboardView";
- private static final boolean DEBUG = false;
+ private static final String TAG = KeyboardView.class.getSimpleName();
private static final boolean DEBUG_SHOW_ALIGN = false;
private static final boolean DEBUG_KEYBOARD_GRID = false;
@@ -115,7 +116,6 @@
private int[] mOffsetInWindow;
private int mOldPreviewKeyIndex = KeyDetector.NOT_A_KEY;
private boolean mShowPreview = true;
- private boolean mShowTouchPoints = true;
private int mPopupPreviewOffsetX;
private int mPopupPreviewOffsetY;
private int mWindowY;
@@ -147,6 +147,9 @@
private final boolean mHasDistinctMultitouch;
private int mOldPointerCount = 1;
+ // Accessibility
+ private boolean mIsAccessibilityEnabled;
+
protected KeyDetector mKeyDetector = new ProximityKeyDetector();
// Swipe gesture detector
@@ -158,18 +161,20 @@
// Drawing
/** Whether the keyboard bitmap needs to be redrawn before it's blitted. **/
private boolean mDrawPending;
- /** The dirty region in the keyboard bitmap */
- private final Rect mDirtyRect = new Rect();
- /** The keyboard bitmap for faster updates */
- private Bitmap mBuffer;
/** Notes if the keyboard just changed, so that we could possibly reallocate the mBuffer. */
private boolean mKeyboardChanged;
+ /** The dirty region in the keyboard bitmap */
+ private final Rect mDirtyRect = new Rect();
+ /** The key to invalidate. */
private Key mInvalidatedKey;
+ /** The dirty region for single key drawing */
+ private final Rect mInvalidatedKeyRect = new Rect();
+ /** The keyboard bitmap for faster updates */
+ private Bitmap mBuffer;
/** The canvas for the above mutable keyboard bitmap */
private Canvas mCanvas;
private final Paint mPaint;
private final Rect mPadding;
- private final Rect mClipRegion = new Rect(0, 0, 0, 0);
// This map caches key label text height in pixel as value and key label text size as map key.
private final HashMap<Integer, Integer> mTextHeightCache = new HashMap<Integer, Integer>();
// Distance from horizontal center of the key, proportional to key label text height and width.
@@ -506,10 +511,9 @@
tracker.setKeyboard(keyboard, mKeys, mKeyHysteresisDistance);
}
requestLayout();
- // Hint to reallocate the buffer if the size changed
mKeyboardChanged = true;
invalidateAllKeys();
- computeProximityThreshold(keyboard, mKeys);
+ mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(keyboard));
mMiniKeyboardCache.clear();
}
@@ -523,7 +527,7 @@
}
/**
- * Return whether the device has distinct multi-touch panel.
+ * Returns whether the device has distinct multi-touch panel.
* @return true if the device has distinct multi-touch panel.
*/
@Override
@@ -532,6 +536,28 @@
}
/**
+ * Enables or disables accessibility.
+ * @param accessibilityEnabled whether or not to enable accessibility
+ */
+ public void setAccessibilityEnabled(boolean accessibilityEnabled) {
+ mIsAccessibilityEnabled = accessibilityEnabled;
+
+ // Propagate this change to all existing pointer trackers.
+ for (PointerTracker tracker : mPointerTrackers) {
+ tracker.setAccessibilityEnabled(accessibilityEnabled);
+ }
+ }
+
+ /**
+ * Returns whether the device has accessibility enabled.
+ * @return true if the device has accessibility enabled.
+ */
+ @Override
+ public boolean isAccessibilityEnabled() {
+ return mIsAccessibilityEnabled;
+ }
+
+ /**
* Enables or disables the key feedback popup. This is a popup that shows a magnified
* version of the depressed key. By default the preview is enabled.
* @param previewEnabled whether or not to enable the key feedback popup
@@ -601,37 +627,6 @@
}
}
- /**
- * Compute the most common key width and use it as proximity key detection threshold.
- * @param keyboard
- * @param keys
- */
- private void computeProximityThreshold(Keyboard keyboard, Key[] keys) {
- if (keyboard == null || keys == null || keys.length == 0) return;
- final HashMap<Integer, Integer> histogram = new HashMap<Integer, Integer>();
- int maxCount = 0;
- int mostCommonWidth = 0;
- for (Key key : keys) {
- final Integer width = key.mWidth + key.mGap;
- Integer count = histogram.get(width);
- if (count == null)
- count = 0;
- histogram.put(width, ++count);
- if (count > maxCount) {
- maxCount = count;
- mostCommonWidth = width;
- }
- }
- mKeyDetector.setProximityThreshold(mostCommonWidth);
- }
-
- @Override
- public void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
- // Release the buffer, if any and it will be reallocated on the next draw
- mBuffer = null;
- }
-
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
@@ -641,19 +636,18 @@
canvas.drawBitmap(mBuffer, 0, 0, null);
}
- @SuppressWarnings("unused")
private void onBufferDraw() {
+ final int width = getWidth();
+ final int height = getHeight();
+ if (width == 0 || height == 0)
+ return;
if (mBuffer == null || mKeyboardChanged) {
- if (mBuffer == null || mKeyboardChanged &&
- (mBuffer.getWidth() != getWidth() || mBuffer.getHeight() != getHeight())) {
- // Make sure our bitmap is at least 1x1
- final int width = Math.max(1, getWidth());
- final int height = Math.max(1, getHeight());
- mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- mCanvas = new Canvas(mBuffer);
- }
- invalidateAllKeys();
mKeyboardChanged = false;
+ mDirtyRect.union(0, 0, width, height);
+ }
+ if (mBuffer == null || mBuffer.getWidth() != width || mBuffer.getHeight() != height) {
+ mBuffer = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBuffer);
}
final Canvas canvas = mCanvas;
canvas.clipRect(mDirtyRect, Op.REPLACE);
@@ -662,30 +656,19 @@
final Paint paint = mPaint;
final Drawable keyBackground = mKeyBackground;
- final Rect clipRegion = mClipRegion;
final Rect padding = mPadding;
final int kbdPaddingLeft = getPaddingLeft();
final int kbdPaddingTop = getPaddingTop();
final Key[] keys = mKeys;
- final Key invalidKey = mInvalidatedKey;
final boolean isManualTemporaryUpperCase = mKeyboard.isManualTemporaryUpperCase();
+ final boolean drawSingleKey = (mInvalidatedKey != null
+ && mInvalidatedKeyRect.contains(mDirtyRect));
- boolean drawSingleKey = false;
- if (invalidKey != null && canvas.getClipBounds(clipRegion)) {
- // TODO we should use Rect.inset and Rect.contains here.
- // Is clipRegion completely contained within the invalidated key?
- if (invalidKey.mX + kbdPaddingLeft - 1 <= clipRegion.left &&
- invalidKey.mY + kbdPaddingTop - 1 <= clipRegion.top &&
- invalidKey.mX + invalidKey.mWidth + kbdPaddingLeft + 1 >= clipRegion.right &&
- invalidKey.mY + invalidKey.mHeight + kbdPaddingTop + 1 >= clipRegion.bottom) {
- drawSingleKey = true;
- }
- }
canvas.drawColor(0x00000000, PorterDuff.Mode.CLEAR);
final int keyCount = keys.length;
for (int i = 0; i < keyCount; i++) {
final Key key = keys[i];
- if (drawSingleKey && invalidKey != key) {
+ if (drawSingleKey && key != mInvalidatedKey) {
continue;
}
int[] drawableState = key.getCurrentDrawableState();
@@ -739,16 +722,23 @@
} else {
positionX = (key.mWidth + padding.left - padding.right) / 2;
paint.setTextAlign(Align.CENTER);
- if (DEBUG_SHOW_ALIGN && label.length() > 1)
- drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ if (DEBUG_SHOW_ALIGN) {
+ if (label.length() > 1)
+ drawVerticalLine(canvas, positionX, rowHeight, 0xc0008080, new Paint());
+ }
}
if (key.mManualTemporaryUpperCaseHintIcon != null && isManualTemporaryUpperCase) {
paint.setColor(mKeyTextColorDisabled);
} else {
paint.setColor(mKeyTextColor);
}
- // Set a drop shadow for the text
- paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+ if (key.mEnabled) {
+ // Set a drop shadow for the text
+ paint.setShadowLayer(mShadowRadius, 0, 0, mShadowColor);
+ } else {
+ // Make label invisible
+ paint.setColor(Color.TRANSPARENT);
+ }
canvas.drawText(label, positionX, baseline, paint);
// Turn off drop shadow
paint.setShadowLayer(0, 0, 0, 0);
@@ -798,6 +788,8 @@
canvas.translate(-key.mX - kbdPaddingLeft, -key.mY - kbdPaddingTop);
}
+ // TODO: Move this function to ProximityInfo for getting rid of public declarations for
+ // GRID_WIDTH and GRID_HEIGHT
if (DEBUG_KEYBOARD_GRID) {
Paint p = new Paint();
p.setStyle(Paint.Style.STROKE);
@@ -811,32 +803,13 @@
canvas.drawLine(0, i * ch, cw * mKeyboard.GRID_WIDTH, i * ch, p);
}
- mInvalidatedKey = null;
// Overlay a dark rectangle to dim the keyboard
if (mMiniKeyboardView != null) {
paint.setColor((int) (mBackgroundDimAmount * 0xFF) << 24);
- canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
+ canvas.drawRect(0, 0, width, height, paint);
}
- if (DEBUG) {
- if (mShowTouchPoints) {
- for (PointerTracker tracker : mPointerTrackers) {
- int startX = tracker.getStartX();
- int startY = tracker.getStartY();
- int lastX = tracker.getLastX();
- int lastY = tracker.getLastY();
- paint.setAlpha(128);
- paint.setColor(0xFFFF0000);
- canvas.drawCircle(startX, startY, 3, paint);
- canvas.drawLine(startX, startY, lastX, lastY, paint);
- paint.setColor(0xFF0000FF);
- canvas.drawCircle(lastX, lastY, 3, paint);
- paint.setColor(0xFF00FF00);
- canvas.drawCircle((startX + lastX) / 2, (startY + lastY) / 2, 2, paint);
- }
- }
- }
-
+ mInvalidatedKey = null;
mDrawPending = false;
mDirtyRect.setEmpty();
}
@@ -1050,12 +1023,11 @@
if (key == null)
return;
mInvalidatedKey = key;
- // TODO we should clean up this and record key's region to use in onBufferDraw.
- mDirtyRect.union(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ mInvalidatedKeyRect.set(0, 0, key.mWidth, key.mHeight);
+ mInvalidatedKeyRect.offset(key.mX + getPaddingLeft(), key.mY + getPaddingTop());
+ mDirtyRect.union(mInvalidatedKeyRect);
onBufferDraw();
- invalidate(key.mX + getPaddingLeft(), key.mY + getPaddingTop(),
- key.mX + key.mWidth + getPaddingLeft(), key.mY + key.mHeight + getPaddingTop());
+ invalidate(mInvalidatedKeyRect);
}
private boolean openPopupIfRequired(int keyIndex, PointerTracker tracker) {
@@ -1084,7 +1056,7 @@
mKeyboardActionListener.onCodeInput(Keyboard.CODE_CAPSLOCK, null, 0, 0);
}
- private void onDoubleTapShiftKey(@SuppressWarnings("unused") PointerTracker tracker) {
+ private void onDoubleTapShiftKey(PointerTracker tracker) {
// When shift key is double tapped, the first tap is correctly processed as usual tap. And
// the second tap is treated as this double tap event, so that we need not mark tracker
// calling setAlreadyProcessed() nor remove the tracker from mPointerQueueueue.
@@ -1122,12 +1094,12 @@
// Nothing to do.
}
@Override
- public void onPress(int primaryCode) {
- mKeyboardActionListener.onPress(primaryCode);
+ public void onPress(int primaryCode, boolean withSliding) {
+ mKeyboardActionListener.onPress(primaryCode, withSliding);
}
@Override
- public void onRelease(int primaryCode) {
- mKeyboardActionListener.onRelease(primaryCode);
+ public void onRelease(int primaryCode, boolean withSliding) {
+ mKeyboardActionListener.onRelease(primaryCode, withSliding);
}
});
// Override default ProximityKeyDetector.
@@ -1266,15 +1238,18 @@
// TODO: cleanup this code into a multi-touch to single-touch event converter class?
// If the device does not have distinct multi-touch support panel, ignore all multi-touch
// events except a transition from/to single-touch.
- if (!mHasDistinctMultitouch && pointerCount > 1 && oldPointerCount > 1) {
+ if ((!mHasDistinctMultitouch || mIsAccessibilityEnabled)
+ && pointerCount > 1 && oldPointerCount > 1) {
return true;
}
// Track the last few movements to look for spurious swipes.
mSwipeTracker.addMovement(me);
- // Gesture detector must be enabled only when mini-keyboard is not on the screen.
- if (mMiniKeyboardView == null
+ // Gesture detector must be enabled only when mini-keyboard is not on the screen and
+ // accessibility is not enabled.
+ // TODO: Reconcile gesture detection and accessibility features.
+ if (mMiniKeyboardView == null && !mIsAccessibilityEnabled
&& mGestureDetector != null && mGestureDetector.onTouchEvent(me)) {
dismissKeyPreview();
mHandler.cancelKeyTimers();
@@ -1319,7 +1294,7 @@
// TODO: cleanup this code into a multi-touch to single-touch event converter class?
// Translate mutli-touch event to single-touch events on the device that has no distinct
// multi-touch panel.
- if (!mHasDistinctMultitouch) {
+ if (!mHasDistinctMultitouch || mIsAccessibilityEnabled) {
// Use only main (id=0) pointer tracker.
PointerTracker tracker = getPointerTracker(0);
if (pointerCount == 1 && oldPointerCount == 2) {
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
index ffb8d64..5820049 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboard.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -31,17 +32,12 @@
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import java.util.List;
import java.util.Locale;
// TODO: We should remove this class
public class LatinKeyboard extends Keyboard {
-
- private static final boolean DEBUG_PREFERRED_LETTER = false;
- private static final String TAG = "LatinKeyboard";
-
public static final int OPACITY_FULLY_OPAQUE = 255;
private static final int SPACE_LED_LENGTH_PERCENT = 80;
@@ -69,15 +65,7 @@
private final Drawable mEnabledShortcutIcon;
private final Drawable mDisabledShortcutIcon;
- private int[] mPrefLetterFrequencies;
- private int mPrefLetter;
- private int mPrefLetterX;
- private int mPrefLetterY;
- private int mPrefDistance;
-
private static final float SPACEBAR_DRAG_THRESHOLD = 0.8f;
- private static final float OVERLAP_PERCENTAGE_LOW_PROB = 0.70f;
- private static final float OVERLAP_PERCENTAGE_HIGH_PROB = 0.85f;
// Minimum width of space key preview (proportional to keyboard width)
private static final float SPACEBAR_POPUP_MIN_RATIO = 0.4f;
// Height in space key the language name will be drawn. (proportional to space key height)
@@ -167,6 +155,8 @@
}
private void updateSpacebarForLocale(boolean isAutoCorrection) {
+ if (mSpaceKey == null)
+ return;
final Resources res = mContext.getResources();
// If application locales are explicitly selected.
if (SubtypeSwitcher.getInstance().needsToDisplayLanguage()) {
@@ -265,7 +255,7 @@
final boolean allowVariableTextSize = true;
final String language = layoutSpacebar(paint, subtypeSwitcher.getInputLocale(),
mButtonArrowLeftIcon, mButtonArrowRightIcon, width, height,
- getTextSizeFromTheme(textStyle, defaultTextSize),
+ getTextSizeFromTheme(mContext.getTheme(), textStyle, defaultTextSize),
allowVariableTextSize);
// Draw language text with shadow
@@ -334,18 +324,9 @@
return mSpaceDragLastDiff > 0 ? 1 : -1;
}
- public void setPreferredLetters(int[] frequencies) {
- mPrefLetterFrequencies = frequencies;
- mPrefLetter = 0;
- }
-
public void keyReleased() {
mCurrentlyInSpace = false;
mSpaceDragLastDiff = 0;
- mPrefLetter = 0;
- mPrefLetterX = 0;
- mPrefLetterY = 0;
- mPrefDistance = Integer.MAX_VALUE;
if (mSpaceKey != null) {
updateLocaleDrag(Integer.MAX_VALUE);
}
@@ -381,80 +362,6 @@
return isOnSpace;
}
}
- } else if (mPrefLetterFrequencies != null) {
- // New coordinate? Reset
- if (mPrefLetterX != x || mPrefLetterY != y) {
- mPrefLetter = 0;
- mPrefDistance = Integer.MAX_VALUE;
- }
- // Handle preferred next letter
- final int[] pref = mPrefLetterFrequencies;
- if (mPrefLetter > 0) {
- if (DEBUG_PREFERRED_LETTER) {
- if (mPrefLetter == code && !key.isOnKey(x, y)) {
- Log.d(TAG, "CORRECTED !!!!!!");
- }
- }
- return mPrefLetter == code;
- } else {
- final boolean isOnKey = key.isOnKey(x, y);
- int[] nearby = getNearestKeys(x, y);
- List<Key> nearbyKeys = getKeys();
- if (isOnKey) {
- // If it's a preferred letter
- if (inPrefList(code, pref)) {
- // Check if its frequency is much lower than a nearby key
- mPrefLetter = code;
- mPrefLetterX = x;
- mPrefLetterY = y;
- for (int i = 0; i < nearby.length; i++) {
- Key k = nearbyKeys.get(nearby[i]);
- if (k != key && inPrefList(k.mCode, pref)) {
- final int dist = distanceFrom(k, x, y);
- if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_LOW_PROB) &&
- (pref[k.mCode] > pref[mPrefLetter] * 3)) {
- mPrefLetter = k.mCode;
- mPrefDistance = dist;
- if (DEBUG_PREFERRED_LETTER) {
- Log.d(TAG, "CORRECTED ALTHOUGH PREFERRED !!!!!!");
- }
- break;
- }
- }
- }
-
- return mPrefLetter == code;
- }
- }
-
- // Get the surrounding keys and intersect with the preferred list
- // For all in the intersection
- // if distance from touch point is within a reasonable distance
- // make this the pref letter
- // If no pref letter
- // return inside;
- // else return thiskey == prefletter;
-
- for (int i = 0; i < nearby.length; i++) {
- Key k = nearbyKeys.get(nearby[i]);
- if (inPrefList(k.mCode, pref)) {
- final int dist = distanceFrom(k, x, y);
- if (dist < (int) (k.mWidth * OVERLAP_PERCENTAGE_HIGH_PROB)
- && dist < mPrefDistance) {
- mPrefLetter = k.mCode;
- mPrefLetterX = x;
- mPrefLetterY = y;
- mPrefDistance = dist;
- }
- }
- }
- // Didn't find any
- if (mPrefLetter == 0) {
- return isOnKey;
- } else {
- return mPrefLetter == code;
- }
- }
}
// Lock into the spacebar
@@ -463,19 +370,6 @@
return key.isOnKey(x, y);
}
- private boolean inPrefList(int code, int[] pref) {
- if (code < pref.length && code >= 0) return pref[code] > 0;
- return false;
- }
-
- private int distanceFrom(Key k, int x, int y) {
- if (y > k.mY && y < k.mY + k.mHeight) {
- return Math.abs(k.mX + k.mWidth / 2 - x);
- } else {
- return Integer.MAX_VALUE;
- }
- }
-
@Override
public int[] getNearestKeys(int x, int y) {
if (mCurrentlyInSpace) {
@@ -487,8 +381,8 @@
}
}
- private int getTextSizeFromTheme(int style, int defValue) {
- TypedArray array = mContext.getTheme().obtainStyledAttributes(
+ private static int getTextSizeFromTheme(Theme theme, int style, int defValue) {
+ TypedArray array = theme.obtainStyledAttributes(
style, new int[] { android.R.attr.textSize });
int textSize = array.getDimensionPixelSize(array.getResourceId(0, 0), defValue);
return textSize;
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index af2fd5c..77e9cae 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -66,7 +66,8 @@
}
}
- public void setLatinKeyboard(LatinKeyboard newKeyboard) {
+ @Override
+ public void setKeyboard(Keyboard newKeyboard) {
final LatinKeyboard oldKeyboard = getLatinKeyboard();
if (oldKeyboard != null) {
// Reset old keyboard state before switching to new keyboard.
@@ -80,7 +81,7 @@
mLastRowY = (newKeyboard.getHeight() * 3) / 4;
}
- public LatinKeyboard getLatinKeyboard() {
+ private LatinKeyboard getLatinKeyboard() {
Keyboard keyboard = getKeyboard();
if (keyboard instanceof LatinKeyboard) {
return (LatinKeyboard)keyboard;
@@ -141,6 +142,13 @@
* KeyboardView.
*/
private boolean handleSuddenJump(MotionEvent me) {
+ // If device has distinct multi touch panel, there is no need to check sudden jump.
+ if (hasDistinctMultitouch())
+ return false;
+ // If accessibiliy is enabled, stop looking for sudden jumps because it interferes
+ // with touch exploration of the keyboard.
+ if (isAccessibilityEnabled())
+ return false;
final int action = me.getAction();
final int x = (int) me.getX();
final int y = (int) me.getY();
diff --git a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
index f04991e..a8750d3 100644
--- a/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MiniKeyboardKeyDetector.java
@@ -35,24 +35,24 @@
}
@Override
- public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
+ public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
- int closestKeyIndex = NOT_A_KEY;
- int closestKeyDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
+ int nearestIndex = NOT_A_KEY;
+ int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
final int keyCount = keys.length;
for (int index = 0; index < keyCount; index++) {
final int dist = keys[index].squaredDistanceToEdge(touchX, touchY);
- if (dist < closestKeyDist) {
- closestKeyIndex = index;
- closestKeyDist = dist;
+ if (dist < nearestDist) {
+ nearestIndex = index;
+ nearestDist = dist;
}
}
- if (allKeys != null && closestKeyIndex != NOT_A_KEY)
- allKeys[0] = keys[closestKeyIndex].mCode;
- return closestKeyIndex;
+ if (allCodes != null && nearestIndex != NOT_A_KEY)
+ allCodes[0] = keys[nearestIndex].mCode;
+ return nearestIndex;
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index a981f72..7468578 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -38,6 +38,7 @@
public void invalidateKey(Key key);
public void showPreview(int keyIndex, PointerTracker tracker);
public boolean hasDistinctMultitouch();
+ public boolean isAccessibilityEnabled();
}
public final int mPointerId;
@@ -68,6 +69,9 @@
private final PointerTrackerKeyState mKeyState;
+ // true if accessibility is enabled in the parent keyboard
+ private boolean mIsAccessibilityEnabled;
+
// true if keyboard layout has been changed.
private boolean mKeyboardLayoutHasBeenChanged;
@@ -89,9 +93,9 @@
// Empty {@link KeyboardActionListener}
private static final KeyboardActionListener EMPTY_LISTENER = new KeyboardActionListener() {
@Override
- public void onPress(int primaryCode) {}
+ public void onPress(int primaryCode, boolean withSliding) {}
@Override
- public void onRelease(int primaryCode) {}
+ public void onRelease(int primaryCode, boolean withSliding) {}
@Override
public void onCodeInput(int primaryCode, int[] keyCodes, int x, int y) {}
@Override
@@ -112,6 +116,7 @@
mKeyDetector = keyDetector;
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
mKeyState = new PointerTrackerKeyState(keyDetector);
+ mIsAccessibilityEnabled = proxy.isAccessibilityEnabled();
mHasDistinctMultitouch = proxy.hasDistinctMultitouch();
mConfigSlidingKeyInputEnabled = res.getBoolean(R.bool.config_sliding_key_input_enabled);
mDelayBeforeKeyRepeatStart = res.getInteger(R.integer.config_delay_before_key_repeat_start);
@@ -128,33 +133,47 @@
mListener = listener;
}
- // Returns true if keyboard has been changed by this callback.
- private boolean callListenerOnPressAndCheckKeyboardLayoutChange(int primaryCode) {
- if (DEBUG_LISTENER)
- Log.d(TAG, "onPress : " + keyCodePrintable(primaryCode));
- mListener.onPress(primaryCode);
- final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
- mKeyboardLayoutHasBeenChanged = false;
- return keyboardLayoutHasBeenChanged;
+ public void setAccessibilityEnabled(boolean accessibilityEnabled) {
+ mIsAccessibilityEnabled = accessibilityEnabled;
}
- private void callListenerOnCodeInput(int primaryCode, int[] keyCodes, int x, int y) {
+ // Returns true if keyboard has been changed by this callback.
+ private boolean callListenerOnPressAndCheckKeyboardLayoutChange(Key key, boolean withSliding) {
+ if (DEBUG_LISTENER)
+ Log.d(TAG, "onPress : " + keyCodePrintable(key.mCode) + " sliding=" + withSliding);
+ if (key.mEnabled) {
+ mListener.onPress(key.mCode, withSliding);
+ final boolean keyboardLayoutHasBeenChanged = mKeyboardLayoutHasBeenChanged;
+ mKeyboardLayoutHasBeenChanged = false;
+ return keyboardLayoutHasBeenChanged;
+ }
+ return false;
+ }
+
+ // Note that we need primaryCode argument because the keyboard may in shifted state and the
+ // primaryCode is different from {@link Key#mCode}.
+ private void callListenerOnCodeInput(Key key, int primaryCode, int[] keyCodes, int x, int y) {
if (DEBUG_LISTENER)
Log.d(TAG, "onCodeInput: " + keyCodePrintable(primaryCode)
+ " codes="+ Arrays.toString(keyCodes) + " x=" + x + " y=" + y);
- mListener.onCodeInput(primaryCode, keyCodes, x, y);
+ if (key.mEnabled)
+ mListener.onCodeInput(primaryCode, keyCodes, x, y);
}
- private void callListenerOnTextInput(CharSequence text) {
+ private void callListenerOnTextInput(Key key) {
if (DEBUG_LISTENER)
- Log.d(TAG, "onTextInput: text=" + text);
- mListener.onTextInput(text);
+ Log.d(TAG, "onTextInput: text=" + key.mOutputText);
+ if (key.mEnabled)
+ mListener.onTextInput(key.mOutputText);
}
- private void callListenerOnRelease(int primaryCode) {
+ // Note that we need primaryCode argument because the keyboard may in shifted state and the
+ // primaryCode is different from {@link Key#mCode}.
+ private void callListenerOnRelease(Key key, int primaryCode, boolean withSliding) {
if (DEBUG_LISTENER)
- Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode));
- mListener.onRelease(primaryCode);
+ Log.d(TAG, "onRelease : " + keyCodePrintable(primaryCode) + " sliding=" + withSliding);
+ if (key.mEnabled)
+ mListener.onRelease(primaryCode, withSliding);
}
private void callListenerOnCancelInput() {
@@ -302,9 +321,10 @@
private void onDownEventInternal(int x, int y, long eventTime) {
int keyIndex = mKeyState.onDownKey(x, y, eventTime);
// Sliding key is allowed when 1) enabled by configuration, 2) this pointer starts sliding
- // from modifier key, or 3) this pointer is on mini-keyboard.
+ // from modifier key, 3) this pointer is on mini-keyboard, or 4) accessibility is enabled.
mIsAllowedSlidingKeyInput = mConfigSlidingKeyInputEnabled || isModifierInternal(keyIndex)
- || mKeyDetector instanceof MiniKeyboardKeyDetector;
+ || mKeyDetector instanceof MiniKeyboardKeyDetector
+ || mIsAccessibilityEnabled;
mKeyboardLayoutHasBeenChanged = false;
mKeyAlreadyProcessed = false;
mIsRepeatableKey = false;
@@ -313,11 +333,13 @@
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update keyIndex according to the new
// keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex].mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(mKeys[keyIndex], false))
keyIndex = mKeyState.onDownKey(x, y, eventTime);
}
if (isValidKeyIndex(keyIndex)) {
- if (mKeys[keyIndex].mRepeatable) {
+ // Accessibility disables key repeat because users may need to pause on a key to hear
+ // its spoken description.
+ if (mKeys[keyIndex].mRepeatable && !mIsAccessibilityEnabled) {
repeatKey(keyIndex);
mHandler.startKeyRepeatTimer(mDelayBeforeKeyRepeatStart, keyIndex, this);
mIsRepeatableKey = true;
@@ -346,7 +368,7 @@
// This onPress call may have changed keyboard layout. Those cases are detected at
// {@link #setKeyboard}. In those cases, we should update keyIndex according to the
// new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
keyIndex = keyState.onMoveKey(x, y);
keyState.onMoveToNewKey(keyIndex, x, y);
startLongPressTimer(keyIndex);
@@ -355,13 +377,13 @@
// onRelease() first to notify that the previous key has been released, then call
// onPress() to notify that the new key is being pressed.
mIsInSlidingKeyInput = true;
- callListenerOnRelease(oldKey.mCode);
+ callListenerOnRelease(oldKey, oldKey.mCode, true);
mHandler.cancelLongPressTimers();
if (mIsAllowedSlidingKeyInput) {
// This onPress call may have changed keyboard layout. Those cases are detected
// at {@link #setKeyboard}. In those cases, we should update keyIndex according
// to the new keyboard layout.
- if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex).mCode))
+ if (callListenerOnPressAndCheckKeyboardLayoutChange(getKey(keyIndex), true))
keyIndex = keyState.onMoveKey(x, y);
keyState.onMoveToNewKey(keyIndex, x, y);
startLongPressTimer(keyIndex);
@@ -390,7 +412,7 @@
// The pointer has been slid out from the previous key, we must call onRelease() to
// notify that the previous key has been released.
mIsInSlidingKeyInput = true;
- callListenerOnRelease(oldKey.mCode);
+ callListenerOnRelease(oldKey, oldKey.mCode, true);
mHandler.cancelLongPressTimers();
if (mIsAllowedSlidingKeyInput) {
keyState.onMoveToNewKey(keyIndex, x ,y);
@@ -490,15 +512,6 @@
return mKeyState.getDownTime();
}
- // These package scope methods are only for debugging purpose.
- /* package */ int getStartX() {
- return mKeyState.getStartX();
- }
-
- /* package */ int getStartY() {
- return mKeyState.getStartY();
- }
-
private boolean isMinorMoveBounce(int x, int y, int newKey) {
if (mKeys == null || mKeyHysteresisDistanceSquared < 0)
throw new IllegalStateException("keyboard and/or hysteresis not set");
@@ -516,8 +529,9 @@
updateKeyGraphics(keyIndex);
// The modifier key, such as shift key, should not be shown as preview when multi-touch is
// supported. On the other hand, if multi-touch is not supported, the modifier key should
- // be shown as preview.
- if (mHasDistinctMultitouch && isModifier()) {
+ // be shown as preview. If accessibility is turned on, the modifier key should be shown as
+ // preview.
+ if (mHasDistinctMultitouch && isModifier() && !mIsAccessibilityEnabled) {
mProxy.showPreview(NOT_A_KEY, this);
} else {
mProxy.showPreview(keyIndex, this);
@@ -525,6 +539,11 @@
}
private void startLongPressTimer(int keyIndex) {
+ // Accessibility disables long press because users are likely to need to pause on a key
+ // for an unspecified duration in order to hear the key's spoken description.
+ if (mIsAccessibilityEnabled) {
+ return;
+ }
Key key = getKey(keyIndex);
if (key.mCode == Keyboard.CODE_SHIFT) {
mHandler.startLongPressShiftTimer(mLongPressShiftKeyTimeout, keyIndex, this);
@@ -548,8 +567,8 @@
return;
}
if (key.mOutputText != null) {
- callListenerOnTextInput(key.mOutputText);
- callListenerOnRelease(key.mCode);
+ callListenerOnTextInput(key);
+ callListenerOnRelease(key, key.mCode, false);
} else {
int code = key.mCode;
final int[] codes = mKeyDetector.newCodeArray();
@@ -570,9 +589,8 @@
codes[1] = codes[0];
codes[0] = code;
}
- if (key.mEnabled)
- callListenerOnCodeInput(code, codes, x, y);
- callListenerOnRelease(code);
+ callListenerOnCodeInput(key, code, codes, x, y);
+ callListenerOnRelease(key, code, false);
}
}
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
index 250bb95..a62ed96 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTrackerKeyState.java
@@ -23,8 +23,6 @@
private final KeyDetector mKeyDetector;
// The position and time at which first down event occurred.
- private int mStartX;
- private int mStartY;
private long mDownTime;
private long mUpTime;
@@ -54,14 +52,6 @@
return mKeyY;
}
- public int getStartX() {
- return mStartX;
- }
-
- public int getStartY() {
- return mStartY;
- }
-
public long getDownTime() {
return mDownTime;
}
@@ -79,8 +69,6 @@
}
public int onDownKey(int x, int y, long eventTime) {
- mStartX = x;
- mStartY = y;
mDownTime = eventTime;
return onMoveToNewKey(onMoveKeyInternal(x, y), x, y);
}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
new file mode 100644
index 0000000..80d6de9
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.keyboard;
+
+import com.android.inputmethod.latin.Utils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ProximityInfo {
+ public static final int MAX_PROXIMITY_CHARS_SIZE = 16;
+
+ private final int mGridWidth;
+ private final int mGridHeight;
+ private final int mGridSize;
+
+ ProximityInfo(int gridWidth, int gridHeight) {
+ mGridWidth = gridWidth;
+ mGridHeight = gridHeight;
+ mGridSize = mGridWidth * mGridHeight;
+ }
+
+ private int mNativeProximityInfo;
+ static {
+ Utils.loadNativeLibrary();
+ }
+ private native int setProximityInfoNative(int maxProximityCharsSize, int displayWidth,
+ int displayHeight, int gridWidth, int gridHeight, int[] proximityCharsArray);
+ private native void releaseProximityInfoNative(int nativeProximityInfo);
+
+ public final void setProximityInfo(int[][] gridNeighborKeyIndexes, int keyboardWidth,
+ int keyboardHeight, List<Key> keys) {
+ int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
+ Arrays.fill(proximityCharsArray, KeyDetector.NOT_A_CODE);
+ for (int i = 0; i < mGridSize; ++i) {
+ final int proximityCharsLength = gridNeighborKeyIndexes[i].length;
+ for (int j = 0; j < proximityCharsLength; ++j) {
+ proximityCharsArray[i * MAX_PROXIMITY_CHARS_SIZE + j] =
+ keys.get(gridNeighborKeyIndexes[i][j]).mCode;
+ }
+ }
+ mNativeProximityInfo = setProximityInfoNative(MAX_PROXIMITY_CHARS_SIZE,
+ keyboardWidth, keyboardHeight, mGridWidth, mGridHeight, proximityCharsArray);
+ }
+
+ // TODO: Get rid of this function's input (keyboard).
+ public int getNativeProximityInfo(Keyboard keyboard) {
+ if (mNativeProximityInfo == 0) {
+ // TODO: Move this function to ProximityInfo and make this private.
+ keyboard.computeNearestNeighbors();
+ }
+ return mNativeProximityInfo;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativeProximityInfo != 0) {
+ releaseProximityInfoNative(mNativeProximityInfo);
+ mNativeProximityInfo = 0;
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
index 0920da2..c3fd198 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityKeyDetector.java
@@ -16,49 +16,106 @@
package com.android.inputmethod.keyboard;
+import android.util.Log;
+
import java.util.Arrays;
public class ProximityKeyDetector extends KeyDetector {
+ private static final String TAG = ProximityKeyDetector.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
private static final int MAX_NEARBY_KEYS = 12;
// working area
- private int[] mDistances = new int[MAX_NEARBY_KEYS];
+ private final int[] mDistances = new int[MAX_NEARBY_KEYS];
+ private final int[] mIndices = new int[MAX_NEARBY_KEYS];
@Override
protected int getMaxNearbyKeys() {
return MAX_NEARBY_KEYS;
}
+ private void initializeNearbyKeys() {
+ Arrays.fill(mDistances, Integer.MAX_VALUE);
+ Arrays.fill(mIndices, NOT_A_KEY);
+ }
+
+ /**
+ * Insert the key into nearby keys buffer and sort nearby keys by ascending order of distance.
+ *
+ * @param keyIndex index of the key.
+ * @param distance distance between the key's edge and user touched point.
+ * @return order of the key in the nearby buffer, 0 if it is the nearest key.
+ */
+ private int sortNearbyKeys(int keyIndex, int distance) {
+ final int[] distances = mDistances;
+ final int[] indices = mIndices;
+ for (int insertPos = 0; insertPos < distances.length; insertPos++) {
+ if (distance < distances[insertPos]) {
+ final int nextPos = insertPos + 1;
+ if (nextPos < distances.length) {
+ System.arraycopy(distances, insertPos, distances, nextPos,
+ distances.length - nextPos);
+ System.arraycopy(indices, insertPos, indices, nextPos,
+ indices.length - nextPos);
+ }
+ distances[insertPos] = distance;
+ indices[insertPos] = keyIndex;
+ return insertPos;
+ }
+ }
+ return distances.length;
+ }
+
+ private void getNearbyKeyCodes(final int[] allCodes) {
+ final Key[] keys = getKeys();
+ final int[] indices = mIndices;
+
+ // allCodes[0] should always have the key code even if it is a non-letter key.
+ if (indices[0] == NOT_A_KEY) {
+ allCodes[0] = NOT_A_CODE;
+ return;
+ }
+
+ int numCodes = 0;
+ for (int j = 0; j < indices.length && numCodes < allCodes.length; j++) {
+ final int index = indices[j];
+ if (index == NOT_A_KEY)
+ break;
+ final int code = keys[index].mCode;
+ // filter out a non-letter key from nearby keys
+ if (code < Keyboard.CODE_SPACE)
+ continue;
+ allCodes[numCodes++] = code;
+ }
+ }
+
@Override
- public int getKeyIndexAndNearbyCodes(int x, int y, int[] allKeys) {
+ public int getKeyIndexAndNearbyCodes(int x, int y, final int[] allCodes) {
final Key[] keys = getKeys();
final int touchX = getTouchX(x);
final int touchY = getTouchY(y);
+ initializeNearbyKeys();
int primaryIndex = NOT_A_KEY;
- final int[] distances = mDistances;
- Arrays.fill(distances, Integer.MAX_VALUE);
for (final int index : mKeyboard.getNearestKeys(touchX, touchY)) {
final Key key = keys[index];
final boolean isInside = key.isInside(touchX, touchY);
- if (isInside)
- primaryIndex = index;
- final int dist = key.squaredDistanceToEdge(touchX, touchY);
- if (isInside || (mProximityCorrectOn && dist < mProximityThresholdSquare)) {
- if (allKeys == null) continue;
- // Find insertion point
- for (int j = 0; j < distances.length; j++) {
- if (distances[j] > dist) {
- final int nextPos = j + 1;
- System.arraycopy(distances, j, distances, nextPos,
- distances.length - nextPos);
- System.arraycopy(allKeys, j, allKeys, nextPos,
- allKeys.length - nextPos);
- distances[j] = dist;
- allKeys[j] = key.mCode;
- break;
- }
- }
+ final int distance = key.squaredDistanceToEdge(touchX, touchY);
+ if (isInside || (mProximityCorrectOn && distance < mProximityThresholdSquare)) {
+ final int insertedPosition = sortNearbyKeys(index, distance);
+ if (insertedPosition == 0 && isInside)
+ primaryIndex = index;
+ }
+ }
+
+ if (allCodes != null && allCodes.length > 0) {
+ getNearbyKeyCodes(allCodes);
+ if (DEBUG) {
+ Log.d(TAG, "x=" + x + " y=" + y
+ + " primary="
+ + (primaryIndex == NOT_A_KEY ? "none" : keys[primaryIndex].mCode)
+ + " codes=" + Arrays.toString(allCodes));
}
}
diff --git a/java/src/com/android/inputmethod/latin/AccessibilityUtils.java b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
new file mode 100644
index 0000000..cd3f9e0
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AccessibilityUtils.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility functions for accessibility support.
+ */
+public class AccessibilityUtils {
+ /** Shared singleton instance. */
+ private static final AccessibilityUtils sInstance = new AccessibilityUtils();
+ private /* final */ LatinIME mService;
+ private /* final */ AccessibilityManager mAccessibilityManager;
+ private /* final */ Map<Integer, CharSequence> mDescriptions;
+
+ /**
+ * Returns a shared instance of AccessibilityUtils.
+ *
+ * @return A shared instance of AccessibilityUtils.
+ */
+ public static AccessibilityUtils getInstance() {
+ return sInstance;
+ }
+
+ /**
+ * Initializes (or re-initializes) the shared instance of AccessibilityUtils
+ * with the specified parent service and preferences.
+ *
+ * @param service The parent input method service.
+ * @param prefs The parent preferences.
+ */
+ public static void init(LatinIME service, SharedPreferences prefs) {
+ sInstance.initialize(service, prefs);
+ }
+
+ private AccessibilityUtils() {
+ // This class is not publicly instantiable.
+ }
+
+ /**
+ * Initializes (or re-initializes) with the specified parent service and
+ * preferences.
+ *
+ * @param service The parent input method service.
+ * @param prefs The parent preferences.
+ */
+ private void initialize(LatinIME service, SharedPreferences prefs) {
+ mService = service;
+ mAccessibilityManager = (AccessibilityManager) service.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ mDescriptions = null;
+ }
+
+ /**
+ * Returns true if accessibility is enabled.
+ *
+ * @return {@code true} if accessibility is enabled.
+ */
+ public boolean isAccessibilityEnabled() {
+ return mAccessibilityManager.isEnabled();
+ }
+
+ /**
+ * Speaks a key's action after it has been released. Does not speak letter
+ * keys since typed keys are already spoken aloud by TalkBack.
+ * <p>
+ * No-op if accessibility is not enabled.
+ * </p>
+ *
+ * @param primaryCode The primary code of the released key.
+ * @param switcher The input method's {@link KeyboardSwitcher}.
+ */
+ public void onRelease(int primaryCode, KeyboardSwitcher switcher) {
+ if (!isAccessibilityEnabled()) {
+ return;
+ }
+
+ int resId = -1;
+
+ switch (primaryCode) {
+ case Keyboard.CODE_SHIFT: {
+ if (switcher.isShiftedOrShiftLocked()) {
+ resId = R.string.description_shift_on;
+ } else {
+ resId = R.string.description_shift_off;
+ }
+ break;
+ }
+
+ case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: {
+ if (switcher.isAlphabetMode()) {
+ resId = R.string.description_symbols_off;
+ } else {
+ resId = R.string.description_symbols_on;
+ }
+ break;
+ }
+ }
+
+ if (resId >= 0) {
+ speakDescription(mService.getResources().getText(resId));
+ }
+ }
+
+ /**
+ * Speaks a key's description for accessibility. If a key has an explicit
+ * description defined in keycodes.xml, that will be used. Otherwise, if the
+ * key is a Unicode character, then its character will be used.
+ * <p>
+ * No-op if accessibility is not enabled.
+ * </p>
+ *
+ * @param primaryCode The primary code of the pressed key.
+ * @param switcher The input method's {@link KeyboardSwitcher}.
+ */
+ public void onPress(int primaryCode, KeyboardSwitcher switcher) {
+ if (!isAccessibilityEnabled()) {
+ return;
+ }
+
+ // TODO Use the current keyboard state to read "Switch to symbols"
+ // instead of just "Symbols" (and similar for shift key).
+ CharSequence description = describeKey(primaryCode);
+ if (description == null && Character.isDefined((char) primaryCode)) {
+ description = Character.toString((char) primaryCode);
+ }
+
+ if (description != null) {
+ speakDescription(description);
+ }
+ }
+
+ /**
+ * Returns a text description for a given key code. If the key does not have
+ * an explicit description, returns <code>null</code>.
+ *
+ * @param keyCode An integer key code.
+ * @return A {@link CharSequence} describing the key or <code>null</code> if
+ * no description is available.
+ */
+ private CharSequence describeKey(int keyCode) {
+ // If not loaded yet, load key descriptions from XML file.
+ if (mDescriptions == null) {
+ mDescriptions = loadDescriptions();
+ }
+
+ return mDescriptions.get(keyCode);
+ }
+
+ /**
+ * Loads key descriptions from resources.
+ */
+ private Map<Integer, CharSequence> loadDescriptions() {
+ final Map<Integer, CharSequence> descriptions = new HashMap<Integer, CharSequence>();
+ final TypedArray array = mService.getResources().obtainTypedArray(R.array.key_descriptions);
+
+ // Key descriptions are stored as a key code followed by a string.
+ for (int i = 0; i < array.length() - 1; i += 2) {
+ int code = array.getInteger(i, 0);
+ CharSequence desc = array.getText(i + 1);
+
+ descriptions.put(code, desc);
+ }
+
+ array.recycle();
+
+ return descriptions;
+ }
+
+ /**
+ * Sends a character sequence to be read aloud.
+ *
+ * @param description The {@link CharSequence} to be read aloud.
+ */
+ private void speakDescription(CharSequence description) {
+ // TODO We need to add an AccessibilityEvent type for IMEs.
+ final AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
+ event.setPackageName(mService.getPackageName());
+ event.setClassName(getClass().getName());
+ event.setAddedCount(description.length());
+ event.getText().add(description);
+
+ mAccessibilityManager.sendAccessibilityEvent(event);
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java
new file mode 100644
index 0000000..092f7ad
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+public class AutoCorrection {
+ private static final boolean DBG = LatinImeLogger.sDBG;
+ private static final String TAG = AutoCorrection.class.getSimpleName();
+ private boolean mHasAutoCorrection;
+ private CharSequence mAutoCorrectionWord;
+ private double mNormalizedScore;
+
+ public void init() {
+ mHasAutoCorrection = false;
+ mAutoCorrectionWord = null;
+ mNormalizedScore = Integer.MIN_VALUE;
+ }
+
+ public boolean hasAutoCorrection() {
+ return mHasAutoCorrection;
+ }
+
+ public CharSequence getAutoCorrectionWord() {
+ return mAutoCorrectionWord;
+ }
+
+ public double getNormalizedScore() {
+ return mNormalizedScore;
+ }
+
+ public void updateAutoCorrectionStatus(Map<String, Dictionary> dictionaries,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions, int[] priorities,
+ CharSequence typedWord, double autoCorrectionThreshold, int correctionMode,
+ CharSequence quickFixedWord, CharSequence whitelistedWord) {
+ if (hasAutoCorrectionForWhitelistedWord(whitelistedWord)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = whitelistedWord;
+ } else if (hasAutoCorrectionForTypedWord(
+ dictionaries, wordComposer, suggestions, typedWord, correctionMode)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = typedWord;
+ } else if (hasAutoCorrectionForQuickFix(quickFixedWord)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = quickFixedWord;
+ } else if (hasAutoCorrectionForBinaryDictionary(wordComposer, suggestions, correctionMode,
+ priorities, typedWord, autoCorrectionThreshold)) {
+ mHasAutoCorrection = true;
+ mAutoCorrectionWord = suggestions.get(0);
+ }
+ }
+
+ public static boolean isValidWord(
+ Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+ if (TextUtils.isEmpty(word)) {
+ return false;
+ }
+ final CharSequence lowerCasedWord = word.toString().toLowerCase();
+ for (final String key : dictionaries.keySet()) {
+ if (key.equals(Suggest.DICT_KEY_WHITELIST)) continue;
+ final Dictionary dictionary = dictionaries.get(key);
+ if (dictionary.isValidWord(word)
+ || (ignoreCase && dictionary.isValidWord(lowerCasedWord))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static boolean isValidWordForAutoCorrection(
+ Map<String, Dictionary> dictionaries, CharSequence word, boolean ignoreCase) {
+ final Dictionary whiteList = dictionaries.get(Suggest.DICT_KEY_WHITELIST);
+ // If "word" is in the whitelist dictionary, it should not be auto corrected.
+ if (whiteList != null && whiteList.isValidWord(word)) {
+ return false;
+ }
+ return isValidWord(dictionaries, word, ignoreCase);
+ }
+
+ private static boolean hasAutoCorrectionForWhitelistedWord(CharSequence whiteListedWord) {
+ return whiteListedWord != null;
+ }
+
+ private boolean hasAutoCorrectionForTypedWord(Map<String, Dictionary> dictionaries,
+ WordComposer wordComposer, ArrayList<CharSequence> suggestions, CharSequence typedWord,
+ int correctionMode) {
+ if (TextUtils.isEmpty(typedWord)) return false;
+ boolean isValidWord = isValidWordForAutoCorrection(dictionaries, typedWord, false);
+ return wordComposer.size() > 1 && suggestions.size() > 0 && isValidWord
+ && (correctionMode == Suggest.CORRECTION_FULL
+ || correctionMode == Suggest.CORRECTION_FULL_BIGRAM);
+ }
+
+ private static boolean hasAutoCorrectionForQuickFix(CharSequence quickFixedWord) {
+ return quickFixedWord != null;
+ }
+
+ private boolean hasAutoCorrectionForBinaryDictionary(WordComposer wordComposer,
+ ArrayList<CharSequence> suggestions, int correctionMode, int[] priorities,
+ CharSequence typedWord, double autoCorrectionThreshold) {
+ if (wordComposer.size() > 1 && (correctionMode == Suggest.CORRECTION_FULL
+ || correctionMode == Suggest.CORRECTION_FULL_BIGRAM)
+ && typedWord != null && suggestions.size() > 0 && priorities.length > 0) {
+ final CharSequence autoCorrectionCandidate = suggestions.get(0);
+ final int autoCorrectionCandidateScore = priorities[0];
+ // TODO: when the normalized score of the first suggestion is nearly equals to
+ // the normalized score of the second suggestion, behave less aggressive.
+ mNormalizedScore = Utils.calcNormalizedScore(
+ typedWord,autoCorrectionCandidate, autoCorrectionCandidateScore);
+ if (DBG) {
+ Log.d(TAG, "Normalized " + typedWord + "," + autoCorrectionCandidate + ","
+ + autoCorrectionCandidateScore + ", " + mNormalizedScore
+ + "(" + autoCorrectionThreshold + ")");
+ }
+ if (mNormalizedScore >= autoCorrectionThreshold) {
+ if (DBG) {
+ Log.d(TAG, "Auto corrected by S-threshold.");
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/java/src/com/android/inputmethod/latin/AutoDictionary.java b/java/src/com/android/inputmethod/latin/AutoDictionary.java
index 307b81d..a00b091 100644
--- a/java/src/com/android/inputmethod/latin/AutoDictionary.java
+++ b/java/src/com/android/inputmethod/latin/AutoDictionary.java
@@ -27,6 +27,7 @@
import android.util.Log;
import java.util.HashMap;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index 813f7d3..08ddd25 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,10 +16,15 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Keyboard;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
+import com.android.inputmethod.keyboard.ProximityInfo;
+
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
+import java.io.File;
import java.util.Arrays;
/**
@@ -33,11 +38,11 @@
* It is necessary to keep it at this value because some languages e.g. German have
* really long words.
*/
- protected static final int MAX_WORD_LENGTH = 48;
+ public static final int MAX_WORD_LENGTH = 48;
+ public static final int MAX_WORDS = 18;
private static final String TAG = "BinaryDictionary";
- private static final int MAX_ALTERNATIVES = 16;
- private static final int MAX_WORDS = 18;
+ private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
private static final int MAX_BIGRAMS = 60;
private static final int TYPED_LETTER_MULTIPLIER = 2;
@@ -46,19 +51,32 @@
private int mDicTypeId;
private int mNativeDict;
private long mDictLength;
- private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_ALTERNATIVES];
+ private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
private final int[] mFrequencies = new int[MAX_WORDS];
private final int[] mFrequencies_bigrams = new int[MAX_BIGRAMS];
- static {
- try {
- System.loadLibrary("jni_latinime");
- } catch (UnsatisfiedLinkError ule) {
- Log.e(TAG, "Could not load native library jni_latinime");
+ private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance();
+
+ private static class Flags {
+ private static class FlagEntry {
+ public final String mName;
+ public final int mValue;
+ public FlagEntry(String name, int value) {
+ mName = name;
+ mValue = value;
+ }
}
+ public static final FlagEntry[] ALL_FLAGS = {
+ // Here should reside all flags that trigger some special processing
+ // These *must* match the definition in UnigramDictionary enum in
+ // unigram_dictionary.h so please update both at the same time.
+ new FlagEntry("requiresGermanUmlautProcessing", 0x1)
+ };
}
+ private int mFlags = 0;
private BinaryDictionary() {
}
@@ -72,47 +90,80 @@
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
synchronized (sInstance) {
sInstance.closeInternal();
- if (resId != 0) {
- sInstance.loadDictionary(context, resId);
+ try {
+ final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
+ if (afd == null) {
+ Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
+ return null;
+ }
+ final String sourceDir = context.getApplicationInfo().sourceDir;
+ final File packagePath = new File(sourceDir);
+ // TODO: Come up with a way to handle a directory.
+ if (!packagePath.isFile()) {
+ Log.e(TAG, "sourceDir is not a file: " + sourceDir);
+ return null;
+ }
+ sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
sInstance.mDicTypeId = dicTypeId;
+ } catch (android.content.res.Resources.NotFoundException e) {
+ Log.e(TAG, "Could not find the resource. resId=" + resId);
+ return null;
+ }
+ }
+ sInstance.initFlags();
+ return sInstance;
+ }
+
+ /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset,
+ long length, int dicTypeId) {
+ synchronized (sInstance) {
+ sInstance.closeInternal();
+ if (dictionary.isFile()) {
+ sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
+ sInstance.mDicTypeId = dicTypeId;
+ } else {
+ Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
+ return null;
}
}
return sInstance;
}
+ private void initFlags() {
+ int flags = 0;
+ for (Flags.FlagEntry entry : Flags.ALL_FLAGS) {
+ if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName))
+ flags |= entry.mValue;
+ }
+ mFlags = flags;
+ }
+
+ static {
+ Utils.loadNativeLibrary();
+ }
+
private native int openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
int maxWords, int maxAlternatives);
private native void closeNative(int dict);
private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
- private native int getSuggestionsNative(int dict, int[] inputCodes, int codesSize,
- char[] outputChars, int[] frequencies,
- int[] nextLettersFrequencies, int nextLettersSize);
+ private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
+ int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
+ int[] frequencies);
private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
int[] inputCodes, int inputCodesLength, char[] outputChars, int[] frequencies,
int maxWordLength, int maxBigrams, int maxAlternatives);
- private final void loadDictionary(Context context, int resId) {
- try {
- final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
- if (afd == null) {
- Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
- return;
- }
- mNativeDict = openNative(context.getApplicationInfo().sourceDir,
- afd.getStartOffset(), afd.getLength(),
+ private final void loadDictionary(String path, long startOffset, long length) {
+ mNativeDict = openNative(path, startOffset, length,
TYPED_LETTER_MULTIPLIER, FULL_WORD_FREQ_MULTIPLIER,
- MAX_WORD_LENGTH, MAX_WORDS, MAX_ALTERNATIVES);
- mDictLength = afd.getLength();
- } catch (android.content.res.Resources.NotFoundException e) {
- Log.e(TAG, "Could not find the resource. resId=" + resId);
- return;
- }
+ MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
+ mDictLength = length;
}
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (mNativeDict == 0) return;
char[] chars = previousWord.toString().toCharArray();
@@ -123,11 +174,11 @@
Arrays.fill(mInputCodes, -1);
int[] alternatives = codes.getCodesAt(0);
System.arraycopy(alternatives, 0, mInputCodes, 0,
- Math.min(alternatives.length, MAX_ALTERNATIVES));
+ Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
mOutputChars_bigrams, mFrequencies_bigrams, MAX_WORD_LENGTH, MAX_BIGRAMS,
- MAX_ALTERNATIVES);
+ MAX_PROXIMITY_CHARS_SIZE);
for (int j = 0; j < count; ++j) {
if (mFrequencies_bigrams[j] < 1) break;
@@ -144,26 +195,9 @@
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
- if (mNativeDict == 0) return;
-
- final int codesSize = codes.size();
- // Won't deal with really long words.
- if (codesSize > MAX_WORD_LENGTH - 1) return;
-
- Arrays.fill(mInputCodes, -1);
- for (int i = 0; i < codesSize; i++) {
- int[] alternatives = codes.getCodesAt(i);
- System.arraycopy(alternatives, 0, mInputCodes, i * MAX_ALTERNATIVES,
- Math.min(alternatives.length, MAX_ALTERNATIVES));
- }
- Arrays.fill(mOutputChars, (char) 0);
- Arrays.fill(mFrequencies, 0);
-
- int count = getSuggestionsNative(mNativeDict, mInputCodes, codesSize, mOutputChars,
- mFrequencies, nextLettersFrequencies,
- nextLettersFrequencies != null ? nextLettersFrequencies.length : 0);
+ public void getWords(final WordComposer codes, final WordCallback callback) {
+ final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
+ mOutputChars, mFrequencies);
for (int j = 0; j < count; ++j) {
if (mFrequencies[j] < 1) break;
@@ -179,6 +213,33 @@
}
}
+ /* package for test */ boolean isValidDictionary() {
+ return mNativeDict != 0;
+ }
+
+ /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
+ char[] outputChars, int[] frequencies) {
+ if (!isValidDictionary()) return -1;
+
+ final int codesSize = codes.size();
+ // Won't deal with really long words.
+ if (codesSize > MAX_WORD_LENGTH - 1) return -1;
+
+ Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
+ for (int i = 0; i < codesSize; i++) {
+ int[] alternatives = codes.getCodesAt(i);
+ System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
+ Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
+ }
+ Arrays.fill(outputChars, (char) 0);
+ Arrays.fill(frequencies, 0);
+
+ return getSuggestionsNative(
+ mNativeDict, keyboard.getProximityInfo(),
+ codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
+ mFlags, outputChars, frequencies);
+ }
+
@Override
public boolean isValidWord(CharSequence word) {
if (word == null) return false;
diff --git a/java/src/com/android/inputmethod/latin/CandidateView.java b/java/src/com/android/inputmethod/latin/CandidateView.java
index fc45c7c..5719b90 100644
--- a/java/src/com/android/inputmethod/latin/CandidateView.java
+++ b/java/src/com/android/inputmethod/latin/CandidateView.java
@@ -54,7 +54,7 @@
private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan();
private static final int MAX_SUGGESTIONS = 16;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
private final ArrayList<View> mWords = new ArrayList<View>();
private final boolean mConfigCandidateHighlightFontColorEnabled;
@@ -226,10 +226,14 @@
}
final String debugString = info.getDebugString();
if (DBG) {
- if (!TextUtils.isEmpty(debugString)) {
+ if (TextUtils.isEmpty(debugString)) {
+ dv.setVisibility(GONE);
+ } else {
dv.setText(debugString);
dv.setVisibility(VISIBLE);
}
+ } else {
+ dv.setVisibility(GONE);
}
} else {
dv.setVisibility(GONE);
@@ -249,8 +253,10 @@
final TextView tv = (TextView)mWords.get(1).findViewById(R.id.candidate_word);
final Spannable word = new SpannableString(autoCorrectedWord);
final int wordLength = word.length();
- word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
- word.setSpan(mInvertedForegroundColorSpan, 0, wordLength, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedBackgroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ word.setSpan(mInvertedForegroundColorSpan, 0, wordLength,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv.setText(word);
mShowingAutoCorrectionInverted = true;
}
@@ -341,9 +347,6 @@
if (mShowingAddToDictionary && index == 0) {
addToDictionary(word);
} else {
- if (!mSuggestions.mIsApplicationSpecifiedCompletions) {
- TextEntryState.acceptedSuggestion(mSuggestions.getWord(0), word);
- }
mService.pickSuggestionManually(index, word);
}
}
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 03211f3..2f1e7c2 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -20,6 +20,7 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
+import android.os.Process;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.util.Log;
@@ -30,6 +31,7 @@
private static final String TAG = "DebugSettings";
private static final String DEBUG_MODE_KEY = "debug_mode";
+ private boolean mServiceNeedsRestart = false;
private CheckBoxPreference mDebugMode;
@Override
@@ -39,16 +41,24 @@
SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
prefs.registerOnSharedPreferenceChangeListener(this);
+ mServiceNeedsRestart = false;
mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY);
updateDebugMode();
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (mServiceNeedsRestart) Process.killProcess(Process.myPid());
+ }
+
+ @Override
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals(DEBUG_MODE_KEY)) {
if (mDebugMode != null) {
mDebugMode.setChecked(prefs.getBoolean(DEBUG_MODE_KEY, false));
updateDebugMode();
+ mServiceNeedsRestart = true;
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java
index 7493359..56f0cc5 100644
--- a/java/src/com/android/inputmethod/latin/Dictionary.java
+++ b/java/src/com/android/inputmethod/latin/Dictionary.java
@@ -61,14 +61,9 @@
* words are added through the callback object.
* @param composer the key sequence to match
* @param callback the callback object to send matched words to as possible candidates
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
* @see WordCallback#addWord(char[], int, int)
*/
- abstract public void getWords(final WordComposer composer, final WordCallback callback,
- int[] nextLettersFrequencies);
+ abstract public void getWords(final WordComposer composer, final WordCallback callback);
/**
* Searches for pairs in the bigram dictionary that matches the previous word and all the
@@ -76,13 +71,9 @@
* @param composer the key sequence to match
* @param previousWord the word before
* @param callback the callback object to send possible word following previous word
- * @param nextLettersFrequencies array of frequencies of next letters that could follow the
- * word so far. For instance, "bracke" can be followed by "t", so array['t'] will have
- * a non-zero value on returning from this method.
- * Pass in null if you don't want the dictionary to look up next letters.
*/
public void getBigrams(final WordComposer composer, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
// empty base implementation
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 0fc86c3..0318175 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -37,7 +37,6 @@
private int mDicTypeId;
private int mMaxDepth;
private int mInputLength;
- private int[] mNextLettersFrequencies;
private StringBuilder sb = new StringBuilder(MAX_WORD_LENGTH);
private static final char QUOTE = '\'';
@@ -191,8 +190,7 @@
}
@Override
- public void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
+ public void getWords(final WordComposer codes, final WordCallback callback) {
synchronized (mUpdatingLock) {
// If we need to update, start off a background task
if (mRequiresReload) startDictionaryLoadingTaskLocked();
@@ -201,7 +199,6 @@
}
mInputLength = codes.size();
- mNextLettersFrequencies = nextLettersFrequencies;
if (mCodes.length < mInputLength) mCodes = new int[mInputLength][];
// Cache the codes so that we don't have to lookup an array list
for (int i = 0; i < mInputLength; i++) {
@@ -228,11 +225,21 @@
/**
* Returns the word's frequency or -1 if not found
*/
- public int getWordFrequency(CharSequence word) {
+ protected int getWordFrequency(CharSequence word) {
Node node = searchNode(mRoots, word, 0, word.length());
return (node == null) ? -1 : node.mFrequency;
}
+ private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) {
+ // The computation itself makes sense for >= 2, but the == 2 case returns 0
+ // anyway so we may as well test against 3 instead and return the constant
+ if (inputLength >= 3) {
+ return (freq * snr * (inputLength - 2)) / (inputLength - 1);
+ } else {
+ return 0;
+ }
+ }
+
/**
* Recursively traverse the tree for words that match the input. Input consists of
* a list of arrays. Each item in the list is one input character position. An input
@@ -246,13 +253,14 @@
* @param completion whether the traversal is now in completion mode - meaning that we've
* exhausted the input and we're looking for all possible suffixes.
* @param snr current weight of the word being formed
- * @param inputIndex position in the input characters. This can be off from the depth in
+ * @param inputIndex position in the input characters. This can be off from the depth in
* case we skip over some punctuations such as apostrophe in the traversal. That is, if you type
* "wouldve", it could be matching "would've", so the depth will be one more than the
* inputIndex
* @param callback the callback class for adding a word
*/
- protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
+ // TODO: Share this routine with the native code for BinaryDictionary
+ protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word,
final int depth, boolean completion, int snr, int inputIndex, int skipPos,
WordCallback callback) {
final int count = roots.mLength;
@@ -278,14 +286,15 @@
if (completion) {
word[depth] = c;
if (terminal) {
- if (!callback.addWord(word, 0, depth + 1, freq * snr, mDicTypeId,
- DataType.UNIGRAM)) {
- return;
+ final int finalFreq;
+ if (skipPos < 0) {
+ finalFreq = freq * snr;
+ } else {
+ finalFreq = computeSkippedWordFinalFreq(freq, snr, mInputLength);
}
- // Add to frequency of next letters for predictive correction
- if (mNextLettersFrequencies != null && depth >= inputIndex && skipPos < 0
- && mNextLettersFrequencies.length > word[inputIndex]) {
- mNextLettersFrequencies[word[inputIndex]]++;
+ if (!callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
+ DataType.UNIGRAM)) {
+ return;
}
}
if (children != null) {
@@ -296,7 +305,7 @@
// Skip the ' and continue deeper
word[depth] = c;
if (children != null) {
- getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
+ getWordsRec(children, codes, word, depth + 1, completion, snr, inputIndex,
skipPos, callback);
}
} else {
@@ -313,10 +322,16 @@
if (codeSize == inputIndex + 1) {
if (terminal) {
- if (INCLUDE_TYPED_WORD_IF_VALID
+ if (INCLUDE_TYPED_WORD_IF_VALID
|| !same(word, depth + 1, codes.getTypedWord())) {
- int finalFreq = freq * snr * addedAttenuation;
- if (skipPos < 0) finalFreq *= FULL_WORD_FREQ_MULTIPLIER;
+ final int finalFreq;
+ if (skipPos < 0) {
+ finalFreq = freq * snr * addedAttenuation
+ * FULL_WORD_FREQ_MULTIPLIER;
+ } else {
+ finalFreq = computeSkippedWordFinalFreq(freq,
+ snr * addedAttenuation, mInputLength);
+ }
callback.addWord(word, 0, depth + 1, finalFreq, mDicTypeId,
DataType.UNIGRAM);
}
@@ -327,7 +342,7 @@
skipPos, callback);
}
} else if (children != null) {
- getWordsRec(children, codes, word, depth + 1,
+ getWordsRec(children, codes, word, depth + 1,
false, snr * addedAttenuation, inputIndex + 1,
skipPos, callback);
}
@@ -427,7 +442,7 @@
@Override
public void getBigrams(final WordComposer codes, final CharSequence previousWord,
- final WordCallback callback, int[] nextLettersFrequencies) {
+ final WordCallback callback) {
if (!reloadDictionaryIfRequired()) {
runReverseLookUp(previousWord, callback);
}
@@ -516,7 +531,7 @@
}
}
- static char toLowerCase(char c) {
+ private static char toLowerCase(char c) {
char baseChar = c;
if (c < BASE_CHARS.length) {
baseChar = BASE_CHARS[c];
@@ -535,7 +550,7 @@
* if c is not a combined character, or the base character if it
* is combined.
*/
- static final char BASE_CHARS[] = {
+ private static final char BASE_CHARS[] = {
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
diff --git a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
index a9f2c2c..5587c68 100644
--- a/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
+++ b/java/src/com/android/inputmethod/latin/InputLanguageSelection.java
@@ -106,7 +106,7 @@
conf.locale = locale;
res.updateConfiguration(conf, res.getDisplayMetrics());
- int mainDicResId = LatinIME.getMainDictionaryResourceId(res);
+ int mainDicResId = Utils.getMainDictionaryResourceId(res);
BinaryDictionary bd = BinaryDictionary.initDictionary(this, mainDicResId, Suggest.DIC_MAIN);
// Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 5ce1b7e..6e76cad 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -18,7 +18,6 @@
import com.android.inputmethod.keyboard.Keyboard;
import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboard;
@@ -40,6 +39,7 @@
import android.net.ConnectivityManager;
import android.os.Debug;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.SystemClock;
import android.os.Vibrator;
@@ -81,13 +81,34 @@
/**
* Input method implementation for Qwerty'ish keyboard.
*/
-public class LatinIME extends InputMethodService implements KeyboardActionListener,
- SharedPreferences.OnSharedPreferenceChangeListener {
- private static final String TAG = "LatinIME";
+public class LatinIME extends InputMethodService implements KeyboardActionListener {
+ private static final String TAG = LatinIME.class.getSimpleName();
private static final boolean PERF_DEBUG = false;
private static final boolean TRACE = false;
private static boolean DEBUG = LatinImeLogger.sDBG;
+ /**
+ * The private IME option used to indicate that no microphone should be
+ * shown for a given text field. For instance, this is specified by the
+ * search dialog when the dialog is already showing a voice search button.
+ *
+ * @deprecated Use {@link LatinIME#IME_OPTION_NO_MICROPHONE} with package name prefixed.
+ */
+ public static final String IME_OPTION_NO_MICROPHONE_COMPAT = "nm";
+
+ /**
+ * The private IME option used to indicate that no microphone should be
+ * shown for a given text field. For instance, this is specified by the
+ * search dialog when the dialog is already showing a voice search button.
+ */
+ public static final String IME_OPTION_NO_MICROPHONE = "noMicrophoneKey";
+
+ /**
+ * The private IME option used to indicate that no settings key should be
+ * shown for a given text field.
+ */
+ public static final String IME_OPTION_NO_SETTINGS_KEY = "noSettingsKey";
+
private static final int DELAY_UPDATE_SUGGESTIONS = 180;
private static final int DELAY_UPDATE_OLD_SUGGESTIONS = 300;
private static final int DELAY_UPDATE_SHIFT_STATE = 300;
@@ -138,6 +159,8 @@
private boolean mIsSettingsSuggestionStripOn;
private boolean mApplicationSpecifiedCompletionOn;
+ private AccessibilityUtils mAccessibilityUtils;
+
private final StringBuilder mComposing = new StringBuilder();
private WordComposer mWord = new WordComposer();
private CharSequence mBestWord;
@@ -145,7 +168,7 @@
private boolean mHasDictionary;
private boolean mJustAddedAutoSpace;
private boolean mAutoCorrectEnabled;
- private boolean mReCorrectionEnabled;
+ private boolean mRecorrectionEnabled;
private boolean mBigramSuggestionEnabled;
private boolean mAutoCorrectOn;
private boolean mVibrateOn;
@@ -158,6 +181,7 @@
private int mConfigDelayBeforeFadeoutLanguageOnSpacebar;
private int mConfigDurationOfFadeoutLanguageOnSpacebar;
private float mConfigFinalFadeoutFactorOfLanguageOnSpacebar;
+ private long mConfigDoubleSpacesTurnIntoPeriodTimeout;
private int mCorrectionMode;
private int mCommittedLength;
@@ -169,7 +193,6 @@
// Indicates whether the suggestion strip is to be on in landscape
private boolean mJustAccepted;
- private boolean mJustReverted;
private int mDeleteCount;
private long mLastKeyTime;
@@ -186,7 +209,6 @@
// Keeps track of most recently inserted text (multi-character key) for reverting
private CharSequence mEnteredText;
- private boolean mRefreshKeyboardRequired;
private final ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
@@ -247,6 +269,7 @@
private static final int MSG_VOICE_RESULTS = 3;
private static final int MSG_FADEOUT_LANGUAGE_ON_SPACEBAR = 4;
private static final int MSG_DISMISS_LANGUAGE_ON_SPACEBAR = 5;
+ private static final int MSG_SPACE_TYPED = 6;
@Override
public void handleMessage(Message msg) {
@@ -323,7 +346,7 @@
removeMessages(MSG_DISMISS_LANGUAGE_ON_SPACEBAR);
final LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
+ final LatinKeyboard keyboard = mKeyboardSwitcher.getLatinKeyboard();
// The language is never displayed when the delay is zero.
if (mConfigDelayBeforeFadeoutLanguageOnSpacebar != 0)
inputView.setSpacebarTextFadeFactor(localeChanged ? 1.0f
@@ -335,6 +358,20 @@
}
}
}
+
+ public void startDoubleSpacesTimer() {
+ removeMessages(MSG_SPACE_TYPED);
+ sendMessageDelayed(obtainMessage(MSG_SPACE_TYPED),
+ mConfigDoubleSpacesTurnIntoPeriodTimeout);
+ }
+
+ public void cancelDoubleSpacesTimer() {
+ removeMessages(MSG_SPACE_TYPED);
+ }
+
+ public boolean isAcceptingDoubleSpaces() {
+ return hasMessages(MSG_SPACE_TYPED);
+ }
}
@Override
@@ -344,13 +381,15 @@
LatinImeLogger.init(this, prefs);
SubtypeSwitcher.init(this, prefs);
KeyboardSwitcher.init(this, prefs);
+ AccessibilityUtils.init(this, prefs);
super.onCreate();
mImm = ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE));
- mInputMethodId = Utils.getInputMethodId(mImm, getApplicationInfo().packageName);
+ mInputMethodId = Utils.getInputMethodId(mImm, getPackageName());
mSubtypeSwitcher = SubtypeSwitcher.getInstance();
mKeyboardSwitcher = KeyboardSwitcher.getInstance();
+ mAccessibilityUtils = AccessibilityUtils.getInstance();
final Resources res = getResources();
mResources = res;
@@ -358,10 +397,10 @@
// If the option should not be shown, do not read the recorrection preference
// 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.default_recorrection_enabled));
+ mRecorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED,
+ res.getBoolean(R.bool.config_default_recorrection_enabled));
} else {
- mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled);
+ mRecorrectionEnabled = res.getBoolean(R.bool.config_default_recorrection_enabled);
}
mConfigEnableShowSubtypeSettings = res.getBoolean(
@@ -374,6 +413,8 @@
R.integer.config_duration_of_fadeout_language_on_spacebar);
mConfigFinalFadeoutFactorOfLanguageOnSpacebar = res.getInteger(
R.integer.config_final_fadeout_percentage_of_language_on_spacebar) / 100.0f;
+ mConfigDoubleSpacesTurnIntoPeriodTimeout = res.getInteger(
+ R.integer.config_double_spaces_turn_into_period_timeout);
Utils.GCUtils.getInstance().reset();
boolean tryGC = true;
@@ -395,21 +436,9 @@
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mReceiver, filter);
mVoiceConnector = VoiceIMEConnector.init(this, prefs, mHandler);
- prefs.registerOnSharedPreferenceChangeListener(this);
- }
-
- /**
- * Returns a main dictionary resource id
- * @return main dictionary resource id
- */
- public static int getMainDictionaryResourceId(Resources res) {
- final String MAIN_DIC_NAME = "main";
- String packageName = LatinIME.class.getPackage().getName();
- return res.getIdentifier(MAIN_DIC_NAME, "raw", packageName);
}
private void initSuggest() {
- updateAutoTextEnabled();
String locale = mSubtypeSwitcher.getInputLocaleStr();
Locale savedLocale = mSubtypeSwitcher.changeSystemLocale(new Locale(locale));
@@ -420,9 +449,10 @@
mQuickFixes = isQuickFixesEnabled(prefs);
final Resources res = mResources;
- int mainDicResId = getMainDictionaryResourceId(res);
+ int mainDicResId = Utils.getMainDictionaryResourceId(res);
mSuggest = new Suggest(this, mainDicResId);
loadAndSetAutoCorrectionThreshold(prefs);
+ updateAutoTextEnabled();
mUserDictionary = new UserDictionary(this, locale);
mSuggest.setUserDictionary(mUserDictionary);
@@ -497,17 +527,6 @@
return container;
}
- private static boolean isPasswordVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
- || variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
- || variation == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- }
-
- private static boolean isEmailVariation(int variation) {
- return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
- || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
- }
-
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
final KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -523,20 +542,16 @@
mSubtypeSwitcher.updateParametersOnStartInputView();
- if (mRefreshKeyboardRequired) {
- mRefreshKeyboardRequired = false;
- onRefreshKeyboard();
- }
-
- TextEntryState.newSession(this);
+ TextEntryState.reset();
// Most such things we decide below in initializeInputAttributesAndGetMode, but we need to
// know now whether this is a password text field, because we need to know now whether we
// want to enable the voice button.
- mVoiceConnector.resetVoiceStates(isPasswordVariation(
- attribute.inputType & InputType.TYPE_MASK_VARIATION));
+ final VoiceIMEConnector voiceIme = mVoiceConnector;
+ voiceIme.resetVoiceStates(Utils.isPasswordInputType(attribute.inputType)
+ || Utils.isVisiblePasswordInputType(attribute.inputType));
- final int mode = initializeInputAttributesAndGetMode(attribute.inputType);
+ initializeInputAttributes(attribute);
inputView.closing();
mEnteredText = null;
@@ -547,9 +562,9 @@
loadSettings(attribute);
if (mSubtypeSwitcher.isKeyboardMode()) {
- switcher.loadKeyboard(mode, attribute.imeOptions,
- mVoiceConnector.isVoiceButtonEnabled(),
- mVoiceConnector.isVoiceButtonOnPrimary());
+ switcher.loadKeyboard(attribute,
+ mSubtypeSwitcher.isShortcutImeEnabled() && voiceIme.isVoiceButtonEnabled(),
+ voiceIme.isVoiceButtonOnPrimary());
switcher.updateShiftState();
}
@@ -560,18 +575,24 @@
updateCorrectionMode();
+ final boolean accessibilityEnabled = mAccessibilityUtils.isAccessibilityEnabled();
+
inputView.setPreviewEnabled(mPopupOn);
inputView.setProximityCorrectionEnabled(true);
+ inputView.setAccessibilityEnabled(accessibilityEnabled);
// If we just entered a text field, maybe it has some old text that requires correction
- checkReCorrectionOnStart();
+ checkRecorrectionOnStart();
inputView.setForeground(true);
- mVoiceConnector.onStartInputView(inputView.getWindowToken());
+ voiceIme.onStartInputView(inputView.getWindowToken());
if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
}
- private int initializeInputAttributesAndGetMode(int inputType) {
+ private void initializeInputAttributes(EditorInfo attribute) {
+ if (attribute == null)
+ return;
+ final int inputType = attribute.inputType;
final int variation = inputType & InputType.TYPE_MASK_VARIATION;
mAutoSpace = false;
mInputTypeNoAutoCorrect = false;
@@ -579,73 +600,52 @@
mApplicationSpecifiedCompletionOn = false;
mApplicationSpecifiedCompletions = null;
- final int mode;
- switch (inputType & InputType.TYPE_MASK_CLASS) {
- case InputType.TYPE_CLASS_NUMBER:
- case InputType.TYPE_CLASS_DATETIME:
- mode = KeyboardId.MODE_NUMBER;
- break;
- case InputType.TYPE_CLASS_PHONE:
- mode = KeyboardId.MODE_PHONE;
- break;
- case InputType.TYPE_CLASS_TEXT:
- mIsSettingsSuggestionStripOn = true;
- // Make sure that passwords are not displayed in candidate view
- if (isPasswordVariation(variation)) {
- mIsSettingsSuggestionStripOn = false;
+ if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
+ mIsSettingsSuggestionStripOn = true;
+ // Make sure that passwords are not displayed in candidate view
+ if (Utils.isPasswordInputType(inputType)
+ || Utils.isVisiblePasswordInputType(inputType)) {
+ mIsSettingsSuggestionStripOn = false;
+ }
+ if (Utils.isEmailVariation(variation)
+ || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
+ mAutoSpace = false;
+ } else {
+ mAutoSpace = true;
+ }
+ if (Utils.isEmailVariation(variation)) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ mIsSettingsSuggestionStripOn = false;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
+ // If it's a browser edit field and auto correct is not ON explicitly, then
+ // disable auto correction, but keep suggestions on.
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
+ mInputTypeNoAutoCorrect = true;
}
- if (isEmailVariation(variation)
- || variation == InputType.TYPE_TEXT_VARIATION_PERSON_NAME) {
- mAutoSpace = false;
- } else {
- mAutoSpace = true;
- }
- if (isEmailVariation(variation)) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_EMAIL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_URL;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
- mode = KeyboardId.MODE_IM;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
- mIsSettingsSuggestionStripOn = false;
- mode = KeyboardId.MODE_TEXT;
- } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
- mode = KeyboardId.MODE_WEB;
- // If it's a browser edit field and auto correct is not ON explicitly, then
- // disable auto correction, but keep suggestions on.
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- } else {
- mode = KeyboardId.MODE_TEXT;
- }
+ }
- // If NO_SUGGESTIONS is set, don't do prediction.
- if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mInputTypeNoAutoCorrect = true;
- }
- // If it's not multiline and the autoCorrect flag is not set, then don't correct
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 &&
- (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
- mInputTypeNoAutoCorrect = true;
- }
- if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
- mIsSettingsSuggestionStripOn = false;
- mApplicationSpecifiedCompletionOn = isFullscreenMode();
- }
- break;
- default:
- mode = KeyboardId.MODE_TEXT;
- break;
+ // If NO_SUGGESTIONS is set, don't do prediction.
+ if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
+ mIsSettingsSuggestionStripOn = false;
+ mInputTypeNoAutoCorrect = true;
+ }
+ // If it's not multiline and the autoCorrect flag is not set, then don't correct
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
+ && (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
+ mInputTypeNoAutoCorrect = true;
+ }
+ if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
+ mIsSettingsSuggestionStripOn = false;
+ mApplicationSpecifiedCompletionOn = isFullscreenMode();
+ }
}
- return mode;
}
- private void checkReCorrectionOnStart() {
- if (!mReCorrectionEnabled) return;
+ private void checkRecorrectionOnStart() {
+ if (!mRecorrectionEnabled) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -713,6 +713,8 @@
if (DEBUG) {
Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart
+ ", ose=" + oldSelEnd
+ + ", lss=" + mLastSelectionStart
+ + ", lse=" + mLastSelectionEnd
+ ", nss=" + newSelStart
+ ", nse=" + newSelEnd
+ ", cs=" + candidatesStart
@@ -723,9 +725,18 @@
// If the current selection in the text view changes, we should
// clear whatever candidate text we have.
- if ((((mComposing.length() > 0 && mHasValidSuggestions)
- || mVoiceConnector.isVoiceInputHighlighted()) && (newSelStart != candidatesEnd
- || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
+ final boolean selectionChanged = (newSelStart != candidatesEnd
+ || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart;
+ final boolean candidatesCleared = candidatesStart == -1 && candidatesEnd == -1;
+ if (((mComposing.length() > 0 && mHasValidSuggestions)
+ || mVoiceConnector.isVoiceInputHighlighted())
+ && (selectionChanged || candidatesCleared)) {
+ if (candidatesCleared) {
+ // If the composing span has been cleared, save the typed word in the history for
+ // recorrection before we reset the candidate strip. Then, we'll be able to show
+ // suggestions for recorrection right away.
+ saveWordInHistory(mComposing);
+ }
mComposing.setLength(0);
mHasValidSuggestions = false;
mHandler.postUpdateSuggestions();
@@ -736,15 +747,10 @@
}
mVoiceConnector.setVoiceInputHighlighted(false);
} else if (!mHasValidSuggestions && !mJustAccepted) {
- switch (TextEntryState.getState()) {
- case ACCEPTED_DEFAULT:
- TextEntryState.reset();
- // $FALL-THROUGH$
- case SPACE_AFTER_PICKED:
+ if (TextEntryState.isAcceptedDefault() || TextEntryState.isSpaceAfterPicked()) {
+ if (TextEntryState.isAcceptedDefault())
+ TextEntryState.reset();
mJustAddedAutoSpace = false; // The user moved the cursor.
- break;
- default:
- break;
}
}
mJustAccepted = false;
@@ -754,18 +760,18 @@
mLastSelectionStart = newSelStart;
mLastSelectionEnd = newSelEnd;
- if (mReCorrectionEnabled && isShowingSuggestionsStrip()) {
+ if (mRecorrectionEnabled && isShowingSuggestionsStrip()) {
// Don't look for corrections if the keyboard is not visible
if (mKeyboardSwitcher.isInputViewShown()) {
// Check if we should go in or out of correction mode.
- if (isSuggestionsRequested() && !mJustReverted
+ if (isSuggestionsRequested()
&& (candidatesStart == candidatesEnd || newSelStart != oldSelStart
- || TextEntryState.isCorrecting())
+ || TextEntryState.isRecorrecting())
&& (newSelStart < newSelEnd - 1 || !mHasValidSuggestions)) {
if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
mHandler.postUpdateOldSuggestions();
} else {
- abortCorrection(false);
+ abortRecorrection(false);
// Show the punctuation suggestions list if the current one is not
// and if not showing "Touch again to save".
if (mCandidateView != null && !isShowingPunctuationList()
@@ -788,7 +794,7 @@
*/
@Override
public void onExtractedTextClicked() {
- if (mReCorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrectionEnabled && isSuggestionsRequested()) return;
super.onExtractedTextClicked();
}
@@ -804,7 +810,7 @@
*/
@Override
public void onExtractedCursorMovement(int dx, int dy) {
- if (mReCorrectionEnabled && isSuggestionsRequested()) return;
+ if (mRecorrectionEnabled && isSuggestionsRequested()) return;
super.onExtractedCursorMovement(dx, dy);
}
@@ -822,17 +828,16 @@
mVoiceConnector.hideVoiceWindow(mConfigurationChanging);
mWordHistory.clear();
super.hideWindow();
- TextEntryState.endSession();
}
@Override
public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) {
if (DEBUG) {
Log.i(TAG, "Received completions:");
- final int count = (applicationSpecifiedCompletions != null)
- ? applicationSpecifiedCompletions.length : 0;
- for (int i = 0; i < count; i++) {
- Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]);
+ if (applicationSpecifiedCompletions != null) {
+ for (int i = 0; i < applicationSpecifiedCompletions.length; i++) {
+ Log.i(TAG, " #" + i + ": " + applicationSpecifiedCompletions[i]);
+ }
}
}
if (mApplicationSpecifiedCompletionOn) {
@@ -963,7 +968,7 @@
}
mCommittedLength = mComposing.length();
TextEntryState.acceptedTyped(mComposing);
- addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
+ addToAutoAndUserBigramDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
}
updateSuggestions();
}
@@ -1011,7 +1016,6 @@
}
private void doubleSpace() {
- //if (!mAutoPunctuate) return;
if (mCorrectionMode == Suggest.CORRECTION_NONE) return;
final InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
@@ -1019,13 +1023,17 @@
if (lastThree != null && lastThree.length() == 3
&& Character.isLetterOrDigit(lastThree.charAt(0))
&& lastThree.charAt(1) == Keyboard.CODE_SPACE
- && lastThree.charAt(2) == Keyboard.CODE_SPACE) {
+ && lastThree.charAt(2) == Keyboard.CODE_SPACE
+ && mHandler.isAcceptingDoubleSpaces()) {
+ mHandler.cancelDoubleSpacesTimer();
ic.beginBatchEdit();
ic.deleteSurroundingText(2, 0);
ic.commitText(". ", 1);
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mJustAddedAutoSpace = true;
+ } else {
+ mHandler.startDoubleSpacesTimer();
}
}
@@ -1105,6 +1113,7 @@
}
mLastKeyTime = when;
KeyboardSwitcher switcher = mKeyboardSwitcher;
+ final boolean accessibilityEnabled = switcher.isAccessibilityEnabled();
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
switch (primaryCode) {
case Keyboard.CODE_DELETE:
@@ -1114,12 +1123,12 @@
break;
case Keyboard.CODE_SHIFT:
// Shift key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
+ if (!distinctMultiTouch || accessibilityEnabled)
switcher.toggleShift();
break;
case Keyboard.CODE_SWITCH_ALPHA_SYMBOL:
// Symbol key is handled in onPress() when device has distinct multi-touch panel.
- if (!distinctMultiTouch)
+ if (!distinctMultiTouch || accessibilityEnabled)
switcher.changeKeyboardMode();
break;
case Keyboard.CODE_CANCEL:
@@ -1157,10 +1166,8 @@
if (isWordSeparator(primaryCode)) {
handleSeparator(primaryCode);
} else {
- handleCharacter(primaryCode, keyCodes);
+ handleCharacter(primaryCode, keyCodes, x, y);
}
- // Cancel the just reverted state
- mJustReverted = false;
}
switcher.onKey(primaryCode);
// Reset after any single keystroke
@@ -1172,7 +1179,7 @@
mVoiceConnector.commitVoiceInput();
InputConnection ic = getCurrentInputConnection();
if (ic == null) return;
- abortCorrection(false);
+ abortRecorrection(false);
ic.beginBatchEdit();
commitTyped(ic);
maybeRemovePreviousPeriod(text);
@@ -1180,7 +1187,6 @@
ic.endBatchEdit();
mKeyboardSwitcher.updateShiftState();
mKeyboardSwitcher.onKey(Keyboard.CODE_DUMMY);
- mJustReverted = false;
mJustAddedAutoSpace = false;
mEnteredText = text;
}
@@ -1220,7 +1226,7 @@
mHandler.postUpdateShiftKeyState();
TextEntryState.backspace();
- if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
+ if (TextEntryState.isUndoCommit()) {
revertLastWord(deleteChar);
ic.endBatchEdit();
return;
@@ -1245,7 +1251,6 @@
}
}
}
- mJustReverted = false;
ic.endBatchEdit();
}
@@ -1273,20 +1278,20 @@
}
}
- private void abortCorrection(boolean force) {
- if (force || TextEntryState.isCorrecting()) {
- TextEntryState.onAbortCorrection();
+ private void abortRecorrection(boolean force) {
+ if (force || TextEntryState.isRecorrecting()) {
+ TextEntryState.onAbortRecorrection();
setCandidatesViewShown(isCandidateStripVisible());
getCurrentInputConnection().finishComposingText();
clearSuggestions();
}
}
- private void handleCharacter(int primaryCode, int[] keyCodes) {
+ private void handleCharacter(int primaryCode, int[] keyCodes, int x, int y) {
mVoiceConnector.handleCharacter();
- if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
- abortCorrection(false);
+ if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isRecorrecting()) {
+ abortRecorrection(false);
}
int code = primaryCode;
@@ -1296,6 +1301,7 @@
mComposing.setLength(0);
saveWordInHistory(mBestWord);
mWord.reset();
+ clearSuggestions();
}
}
KeyboardSwitcher switcher = mKeyboardSwitcher;
@@ -1323,7 +1329,7 @@
mWord.setFirstCharCapitalized(true);
}
mComposing.append((char) code);
- mWord.add(code, keyCodes);
+ mWord.add(code, keyCodes, x, y);
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
// If it's the first letter, make note of auto-caps state
@@ -1354,14 +1360,14 @@
final InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
- abortCorrection(false);
+ abortRecorrection(false);
}
if (mHasValidSuggestions) {
// In certain languages where single quote is a separator, it's better
// not to auto correct, but accept the typed word. For instance,
// in Italian dov' should not be expanded to dove' because the elision
// requires the last vowel to be removed.
- if (mAutoCorrectOn && primaryCode != '\'' && !mJustReverted) {
+ if (mAutoCorrectOn && primaryCode != '\'') {
pickedDefault = pickDefaultSuggestion();
// Picked the suggestion by the space key. We consider this
// as "added an auto space".
@@ -1380,14 +1386,12 @@
// Handle the case of ". ." -> " .." with auto-space if necessary
// before changing the TextEntryState.
- if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode == Keyboard.CODE_PERIOD) {
+ if (TextEntryState.isPunctuationAfterAccepted() && primaryCode == Keyboard.CODE_PERIOD) {
reswapPeriodAndSpace();
}
TextEntryState.typedCharacter((char) primaryCode, true);
- if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
- && primaryCode != Keyboard.CODE_ENTER) {
+ if (TextEntryState.isPunctuationAfterAccepted() && primaryCode != Keyboard.CODE_ENTER) {
swapPunctuationAndSpace();
} else if (isSuggestionsRequested() && primaryCode == Keyboard.CODE_SPACE) {
doubleSpace();
@@ -1419,12 +1423,10 @@
LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
if (inputView != null)
inputView.closing();
- TextEntryState.endSession();
}
private void saveWordInHistory(CharSequence result) {
if (mWord.size() <= 1) {
- mWord.reset();
return;
}
// Skip if result is null. It happens in some edge case.
@@ -1457,7 +1459,7 @@
private boolean isCandidateStripVisible() {
if (mCandidateView == null)
return false;
- if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isCorrecting())
+ if (mCandidateView.isShowingAddToDictionaryHint() || TextEntryState.isRecorrecting())
return true;
if (!isShowingSuggestionsStrip())
return false;
@@ -1467,26 +1469,21 @@
}
public void switchToKeyboardView() {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- if (DEBUG) {
- Log.d(TAG, "Switch to keyboard view.");
- }
- View v = mKeyboardSwitcher.getInputView();
- if (v != null) {
- // Confirms that the keyboard view doesn't have parent view.
- ViewParent p = v.getParent();
- if (p != null && p instanceof ViewGroup) {
- ((ViewGroup) p).removeView(v);
- }
- setInputView(v);
- }
- setCandidatesViewShown(isCandidateStripVisible());
- updateInputViewShown();
- mHandler.postUpdateSuggestions();
+ if (DEBUG) {
+ Log.d(TAG, "Switch to keyboard view.");
+ }
+ View v = mKeyboardSwitcher.getInputView();
+ if (v != null) {
+ // Confirms that the keyboard view doesn't have parent view.
+ ViewParent p = v.getParent();
+ if (p != null && p instanceof ViewGroup) {
+ ((ViewGroup) p).removeView(v);
}
- });
+ setInputView(v);
+ }
+ setCandidatesViewShown(isCandidateStripVisible());
+ updateInputViewShown();
+ mHandler.postUpdateSuggestions();
}
public void clearSuggestions() {
@@ -1508,8 +1505,6 @@
}
public void updateSuggestions() {
- mKeyboardSwitcher.setPreferredLetters(null);
-
// Check if we have a suggestion engine attached.
if ((mSuggest == null || !isSuggestionsRequested())
&& !mVoiceConnector.isVoiceInputHighlighted()) {
@@ -1528,36 +1523,30 @@
}
private void showCorrections(WordAlternatives alternatives) {
- mKeyboardSwitcher.setPreferredLetters(null);
SuggestedWords.Builder builder = alternatives.getAlternatives();
builder.setTypedWordValid(false).setHasMinimalSuggestion(false);
showSuggestions(builder.build(), alternatives.getOriginalWord());
}
private void showSuggestions(WordComposer word) {
- // TODO Maybe need better way of retrieving previous word
+ // TODO: May need a better way of retrieving previous word
CharSequence prevWord = EditingUtils.getPreviousWord(getCurrentInputConnection(),
mWordSeparators);
SuggestedWords.Builder builder = mSuggest.getSuggestedWordBuilder(
mKeyboardSwitcher.getInputView(), word, prevWord);
- int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
- mKeyboardSwitcher.setPreferredLetters(nextLettersFrequencies);
-
- boolean correctionAvailable = !mInputTypeNoAutoCorrect && !mJustReverted
- && mSuggest.hasAutoCorrection();
+ boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasAutoCorrection();
final CharSequence typedWord = word.getTypedWord();
- // If we're in basic correct
- final boolean typedWordValid = mSuggest.isValidWord(typedWord) ||
- (preferCapitalization()
- && mSuggest.isValidWord(typedWord.toString().toLowerCase()));
+ // Here, we want to promote a whitelisted word if exists.
+ final boolean typedWordValid = AutoCorrection.isValidWordForAutoCorrection(
+ mSuggest.getUnigramDictionaries(), typedWord, preferCapitalization());
if (mCorrectionMode == Suggest.CORRECTION_FULL
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
correctionAvailable |= typedWordValid;
}
// Don't auto-correct words with multiple capital letter
correctionAvailable &= !word.isMostlyCaps();
- correctionAvailable &= !TextEntryState.isCorrecting();
+ correctionAvailable &= !TextEntryState.isRecorrecting();
// Basically, we update the suggestion strip only when suggestion count > 1. However,
// there is an exception: We update the suggestion strip whenever typed word's length
@@ -1604,7 +1593,7 @@
mJustAccepted = true;
pickSuggestion(mBestWord);
// Add the word to the auto dictionary if it's not a known word
- addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
+ addToAutoAndUserBigramDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
return true;
}
@@ -1615,7 +1604,7 @@
SuggestedWords suggestions = mCandidateView.getSuggestions();
mVoiceConnector.flushAndLogAllTextModificationCounters(index, suggestion, mWordSeparators);
- final boolean correcting = TextEntryState.isCorrecting();
+ final boolean recorrecting = TextEntryState.isRecorrecting();
InputConnection ic = getCurrentInputConnection();
if (ic != null) {
ic.beginBatchEdit();
@@ -1657,24 +1646,35 @@
pickSuggestion(suggestion);
// Add the word to the auto dictionary if it's not a known word
if (index == 0) {
- addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
+ addToAutoAndUserBigramDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
} else {
- addToBigramDictionary(suggestion, 1);
+ addToOnlyBigramDictionary(suggestion, 1);
}
LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(),
index, suggestions.mWords);
TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
// Follow it with a space
- if (mAutoSpace && !correcting) {
+ if (mAutoSpace && !recorrecting) {
sendSpace();
mJustAddedAutoSpace = true;
}
- final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
- && !mSuggest.isValidWord(suggestion)
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
+ // We should show the hint if the user pressed the first entry AND either:
+ // - There is no dictionary (we know that because we tried to load it => null != mSuggest
+ // AND mHasDictionary is false)
+ // - There is a dictionary and the word is not in it
+ // Please note that if mSuggest is null, it means that everything is off: suggestion
+ // and correction, so we shouldn't try to show the hint
+ // We used to look at mCorrectionMode here, but showing the hint should have nothing
+ // to do with the autocorrection setting.
+ final boolean showingAddToDictionaryHint = index == 0 && mSuggest != null
+ // If there is no dictionary the hint should be shown.
+ && (!mHasDictionary
+ // If "suggestion" is not in the dictionary, the hint should be shown.
+ || !AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), suggestion, true));
- if (!correcting) {
+ if (!recorrecting) {
// Fool the state watcher so that a subsequent backspace will not do a revert, unless
// we just did a correction, in which case we need to stay in
// TextEntryState.State.PICKED_SUGGESTION state.
@@ -1712,7 +1712,6 @@
saveWordInHistory(suggestion);
mHasValidSuggestions = false;
mCommittedLength = suggestion.length();
- switcher.setPreferredLetters(null);
}
/**
@@ -1725,6 +1724,7 @@
// If we didn't find a match, search for result in typed word history
WordComposer foundWord = null;
WordAlternatives alternatives = null;
+ // Search old suggestions to suggest re-corrected suggestions.
for (WordAlternatives entry : mWordHistory) {
if (TextUtils.equals(entry.getChosenWord(), touching.mWord)) {
if (entry instanceof TypedWordAlternatives) {
@@ -1734,15 +1734,15 @@
break;
}
}
- // If we didn't find a match, at least suggest corrections.
+ // If we didn't find a match, at least suggest corrections as re-corrected suggestions.
if (foundWord == null
- && (mSuggest.isValidWord(touching.mWord)
- || mSuggest.isValidWord(touching.mWord.toString().toLowerCase()))) {
+ && (AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), touching.mWord, true))) {
foundWord = new WordComposer();
for (int i = 0; i < touching.mWord.length(); i++) {
foundWord.add(touching.mWord.charAt(i), new int[] {
touching.mWord.charAt(i)
- });
+ }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
}
foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.mWord.charAt(0)));
}
@@ -1779,19 +1779,19 @@
if (!mVoiceConnector.applyVoiceAlternatives(touching)
&& !applyTypedAlternatives(touching)) {
- abortCorrection(true);
+ abortRecorrection(true);
} else {
- TextEntryState.selectedForCorrection();
+ TextEntryState.selectedForRecorrection();
EditingUtils.underlineWord(ic, touching);
}
ic.endBatchEdit();
} else {
- abortCorrection(true);
+ abortRecorrection(true);
setPunctuationSuggestions(); // Show the punctuation suggestions list
}
} else {
- abortCorrection(true);
+ abortRecorrection(true);
}
}
@@ -1800,21 +1800,22 @@
setCandidatesViewShown(isCandidateStripVisible());
}
- private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
+ private void addToAutoAndUserBigramDictionaries(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, false);
}
- private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
+ private void addToOnlyBigramDictionary(CharSequence suggestion, int frequencyDelta) {
checkAddToDictionary(suggestion, frequencyDelta, true);
}
/**
* Adds to the UserBigramDictionary and/or AutoDictionary
- * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
+ * @param selectedANotTypedWord true if it should be added to bigram dictionary if possible
*/
private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
- boolean addToBigramDictionary) {
+ boolean selectedANotTypedWord) {
if (suggestion == null || suggestion.length() < 1) return;
+
// Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
// adding words in situations where the user or application really didn't
// want corrections enabled or learned.
@@ -1822,9 +1823,14 @@
|| mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
return;
}
- if (!addToBigramDictionary && mAutoDictionary.isValidWord(suggestion)
- || (!mSuggest.isValidWord(suggestion.toString())
- && !mSuggest.isValidWord(suggestion.toString().toLowerCase()))) {
+
+ final boolean selectedATypedWordAndItsInAutoDic =
+ !selectedANotTypedWord && mAutoDictionary.isValidWord(suggestion);
+ final boolean isValidWord = AutoCorrection.isValidWord(
+ mSuggest.getUnigramDictionaries(), suggestion, true);
+ final boolean needsToAddToAutoDictionary = selectedATypedWordAndItsInAutoDic
+ || !isValidWord;
+ if (needsToAddToAutoDictionary) {
mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
}
@@ -1864,7 +1870,6 @@
final int length = mComposing.length();
if (!mHasValidSuggestions && length > 0) {
final InputConnection ic = getCurrentInputConnection();
- mJustReverted = true;
final CharSequence punctuation = ic.getTextBeforeCursor(1, 0);
if (deleteChar) ic.deleteSurroundingText(1, 0);
int toDelete = mCommittedLength;
@@ -1892,7 +1897,6 @@
mHandler.postUpdateSuggestions();
} else {
sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
- mJustReverted = false;
}
}
@@ -1930,28 +1934,11 @@
mSubtypeSwitcher.toggleLanguage(reset, next);
}
// Reload keyboard because the current language has been changed.
- KeyboardSwitcher switcher = mKeyboardSwitcher;
- final EditorInfo attribute = getCurrentInputEditorInfo();
- final int mode = initializeInputAttributesAndGetMode((attribute != null)
- ? attribute.inputType : 0);
- final int imeOptions = (attribute != null) ? attribute.imeOptions : 0;
- switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(),
+ mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(),
+ mSubtypeSwitcher.isShortcutImeEnabled() && mVoiceConnector.isVoiceButtonEnabled(),
mVoiceConnector.isVoiceButtonOnPrimary());
initSuggest();
- switcher.updateShiftState();
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- mSubtypeSwitcher.onSharedPreferenceChanged(sharedPreferences, key);
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mRefreshKeyboardRequired = true;
- } else if (Settings.PREF_RECORRECTION_ENABLED.equals(key)) {
- mReCorrectionEnabled = sharedPreferences.getBoolean(
- Settings.PREF_RECORRECTION_ENABLED,
- mResources.getBoolean(R.bool.default_recorrection_enabled));
- }
+ mKeyboardSwitcher.updateShiftState();
}
@Override
@@ -1961,7 +1948,7 @@
}
@Override
- public void onPress(int primaryCode) {
+ public void onPress(int primaryCode, boolean withSliding) {
if (mKeyboardSwitcher.isVibrateAndSoundFeedbackRequired()) {
vibrate();
playKeyClick(primaryCode);
@@ -1969,25 +1956,27 @@
KeyboardSwitcher switcher = mKeyboardSwitcher;
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onPressShift();
+ switcher.onPressShift(withSliding);
} else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
switcher.onPressSymbol();
} else {
switcher.onOtherKeyPressed();
}
+ mAccessibilityUtils.onPress(primaryCode, switcher);
}
@Override
- public void onRelease(int primaryCode) {
+ public void onRelease(int primaryCode, boolean withSliding) {
KeyboardSwitcher switcher = mKeyboardSwitcher;
// Reset any drag flags in the keyboard
switcher.keyReleased();
final boolean distinctMultiTouch = switcher.hasDistinctMultitouch();
if (distinctMultiTouch && primaryCode == Keyboard.CODE_SHIFT) {
- switcher.onReleaseShift();
+ switcher.onReleaseShift(withSliding);
} else if (distinctMultiTouch && primaryCode == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) {
switcher.onReleaseSymbol();
}
+ mAccessibilityUtils.onRelease(primaryCode, switcher);
}
@@ -2067,6 +2056,7 @@
}
private void updateCorrectionMode() {
+ // TODO: cleanup messy flags
mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes)
&& !mInputTypeNoAutoCorrect && mHasDictionary;
@@ -2082,7 +2072,7 @@
private void updateAutoTextEnabled() {
if (mSuggest == null) return;
- mSuggest.setAutoTextEnabled(mQuickFixes
+ mSuggest.setQuickFixesEnabled(mQuickFixes
&& SubtypeSwitcher.getInstance().isSystemLanguageSameAsInputLanguage());
}
@@ -2121,7 +2111,8 @@
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
mVibrateOn = vibrator != null && vibrator.hasVibrator()
&& prefs.getBoolean(Settings.PREF_VIBRATE_ON, false);
- mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, false);
+ mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON,
+ mResources.getBoolean(R.bool.config_default_sound_enabled));
mPopupOn = isPopupEnabled(prefs);
mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true);
@@ -2272,10 +2263,10 @@
di.dismiss();
switch (position) {
case 0:
- launchSettings();
+ mImm.showInputMethodPicker();
break;
case 1:
- mImm.showInputMethodPicker();
+ launchSettings();
break;
}
}
@@ -2285,6 +2276,8 @@
private void showOptionsMenuInternal(CharSequence title, CharSequence[] items,
DialogInterface.OnClickListener listener) {
+ final IBinder windowToken = mKeyboardSwitcher.getInputView().getWindowToken();
+ if (windowToken == null) return;
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_dialog_keyboard);
@@ -2295,7 +2288,7 @@
mOptionsDialog.setCanceledOnTouchOutside(true);
Window window = mOptionsDialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
- lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
+ lp.token = windowToken;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 12338ce..341d5ad 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -225,7 +225,9 @@
final String action;
if (android.os.Build.VERSION.SDK_INT
>= /* android.os.Build.VERSION_CODES.HONEYCOMB */ 11) {
- action = "android.settings.INPUT_METHOD_AND_SUBTYPE_ENABLER";
+ // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS
+ // TODO: Can this be a constant instead of literal String constant?
+ action = "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS";
} else {
action = "com.android.inputmethod.latin.INPUT_LANGUAGE_SELECTION";
}
diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
index f4262cc..dc14d77 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -18,7 +18,6 @@
import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.LatinKeyboard;
-import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.voice.SettingsUtil;
import com.android.inputmethod.voice.VoiceIMEConnector;
import com.android.inputmethod.voice.VoiceInput;
@@ -47,7 +46,7 @@
public class SubtypeSwitcher {
private static boolean DBG = LatinImeLogger.sDBG;
- private static final String TAG = "SubtypeSwitcher";
+ private static final String TAG = SubtypeSwitcher.class.getSimpleName();
private static final char LOCALE_SEPARATER = '_';
private static final String KEYBOARD_MODE = "keyboard";
@@ -75,10 +74,10 @@
private InputMethodInfo mShortcutInputMethodInfo;
private InputMethodSubtype mShortcutSubtype;
private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod;
+ private InputMethodSubtype mCurrentSubtype;
private Locale mSystemLocale;
private Locale mInputLocale;
private String mInputLocaleStr;
- private String mMode;
private VoiceInput mVoiceInput;
/*-----------------------------------------------------------*/
@@ -111,8 +110,7 @@
mSystemLocale = null;
mInputLocale = null;
mInputLocaleStr = null;
- // Mode is initialized to KEYBOARD_MODE, in case that LatinIME can't obtain currentSubtype
- mMode = KEYBOARD_MODE;
+ mCurrentSubtype = null;
mAllEnabledSubtypesOfCurrentInputMethod = null;
// TODO: Voice input should be created here
mVoiceInput = null;
@@ -146,6 +144,7 @@
// Reload enabledSubtypes from the framework.
private void updateEnabledSubtypes() {
+ final String currentMode = getCurrentSubtypeMode();
boolean foundCurrentSubtypeBecameDisabled = true;
mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList(
null, true);
@@ -158,7 +157,7 @@
if (mLocaleSplitter.hasNext()) {
mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next());
}
- if (locale.equals(mInputLocaleStr) && mode.equals(mMode)) {
+ if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) {
foundCurrentSubtypeBecameDisabled = false;
}
if (KEYBOARD_MODE.equals(ims.getMode())) {
@@ -169,7 +168,7 @@
&& mIsSystemLanguageSameAsInputLanguage);
if (foundCurrentSubtypeBecameDisabled) {
if (DBG) {
- Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + mMode);
+ Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode);
Log.w(TAG, "Last subtype was disabled. Update to the current one.");
}
updateSubtype(mImm.getCurrentInputMethodSubtype());
@@ -210,9 +209,10 @@
public void updateSubtype(InputMethodSubtype newSubtype) {
final String newLocale;
final String newMode;
+ final String oldMode = getCurrentSubtypeMode();
if (newSubtype == null) {
// Normally, newSubtype shouldn't be null. But just in case newSubtype was null,
- // fallback to the default locale and mode.
+ // fallback to the default locale.
Log.w(TAG, "Couldn't get the current subtype.");
newLocale = "en_US";
newMode = KEYBOARD_MODE;
@@ -222,7 +222,7 @@
}
if (DBG) {
Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode
- + ", from: " + mInputLocaleStr + ", " + mMode);
+ + ", from: " + mInputLocaleStr + ", " + oldMode);
}
boolean languageChanged = false;
if (!newLocale.equals(mInputLocaleStr)) {
@@ -232,13 +232,12 @@
updateInputLocale(newLocale);
}
boolean modeChanged = false;
- String oldMode = mMode;
- if (!newMode.equals(mMode)) {
- if (mMode != null) {
+ if (!newMode.equals(oldMode)) {
+ if (oldMode != null) {
modeChanged = true;
}
- mMode = newMode;
}
+ mCurrentSubtype = newSubtype;
// If the old mode is voice input, we need to reset or cancel its status.
// We cancel its status when we change mode, while we reset otherwise.
@@ -263,7 +262,7 @@
triggerVoiceIME();
}
} else {
- Log.w(TAG, "Unknown subtype mode: " + mMode);
+ Log.w(TAG, "Unknown subtype mode: " + newMode);
if (VOICE_MODE.equals(oldMode) && mVoiceInput != null) {
// We need to reset the voice input to release the resources and to reset its status
// as it is not the current input mode.
@@ -356,11 +355,27 @@
return false;
}
- public boolean isShortcutAvailable() {
+ public boolean isShortcutImeEnabled() {
if (mShortcutInputMethodInfo == null)
return false;
- if (mShortcutSubtype != null && contains(mShortcutSubtype.getExtraValue().split(","),
- SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
+ if (mShortcutSubtype == null)
+ return true;
+ final boolean allowsImplicitlySelectedSubtypes = true;
+ for (final InputMethodSubtype enabledSubtype : mImm.getEnabledInputMethodSubtypeList(
+ mShortcutInputMethodInfo, allowsImplicitlySelectedSubtypes)) {
+ if (enabledSubtype.equals(mShortcutSubtype))
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isShortcutImeReady() {
+ if (mShortcutInputMethodInfo == null)
+ return false;
+ if (mShortcutSubtype == null)
+ return true;
+ if (contains(mShortcutSubtype.getExtraValue().split(","),
+ SUBTYPE_EXTRAVALUE_REQUIRE_NETWORK_CONNECTIVITY)) {
return mIsNetworkConnected;
}
return true;
@@ -371,12 +386,10 @@
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
mIsNetworkConnected = !noConnection;
- final LatinKeyboardView inputView = KeyboardSwitcher.getInstance().getInputView();
- if (inputView != null) {
- final LatinKeyboard keyboard = inputView.getLatinKeyboard();
- if (keyboard != null) {
- keyboard.updateShortcutKey(isShortcutAvailable(), inputView);
- }
+ final KeyboardSwitcher switcher = KeyboardSwitcher.getInstance();
+ final LatinKeyboard keyboard = switcher.getLatinKeyboard();
+ if (keyboard != null) {
+ keyboard.updateShortcutKey(isShortcutImeReady(), switcher.getInputView());
}
}
@@ -426,8 +439,15 @@
if (mConfigUseSpacebarLanguageSwitcher) {
return mLanguageSwitcher.getEnabledLanguages();
} else {
+ int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size();
+ // Workaround for explicitly specifying the voice language
+ if (enabledLanguageCount == 1) {
+ mEnabledLanguagesOfCurrentInputMethod.add(
+ mEnabledLanguagesOfCurrentInputMethod.get(0));
+ ++enabledLanguageCount;
+ }
return mEnabledLanguagesOfCurrentInputMethod.toArray(
- new String[mEnabledLanguagesOfCurrentInputMethod.size()]);
+ new String[enabledLanguageCount]);
}
}
@@ -465,14 +485,6 @@
}
}
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (mConfigUseSpacebarLanguageSwitcher) {
- if (Settings.PREF_SELECTED_LANGUAGES.equals(key)) {
- mLanguageSwitcher.loadLocales(sharedPreferences);
- }
- }
- }
-
/**
* Change system locale for this application
* @param newLocale
@@ -487,7 +499,7 @@
}
public boolean isKeyboardMode() {
- return KEYBOARD_MODE.equals(mMode);
+ return KEYBOARD_MODE.equals(getCurrentSubtypeMode());
}
@@ -510,7 +522,7 @@
}
public boolean isVoiceMode() {
- return VOICE_MODE.equals(mMode);
+ return null == mCurrentSubtype ? false : VOICE_MODE.equals(getCurrentSubtypeMode());
}
private void triggerVoiceIME() {
@@ -576,6 +588,30 @@
}
}
+ /////////////////////////////
+ // Other utility functions //
+ /////////////////////////////
+
+ public String getCurrentSubtypeExtraValue() {
+ // If null, return what an empty ExtraValue would return : the empty string.
+ return null != mCurrentSubtype ? mCurrentSubtype.getExtraValue() : "";
+ }
+
+ public boolean currentSubtypeContainsExtraValueKey(String key) {
+ // If null, return what an empty ExtraValue would return : false.
+ return null != mCurrentSubtype ? mCurrentSubtype.containsExtraValueKey(key) : false;
+ }
+
+ public String getCurrentSubtypeExtraValueOf(String key) {
+ // If null, return what an empty ExtraValue would return : null.
+ return null != mCurrentSubtype ? mCurrentSubtype.getExtraValueOf(key) : null;
+ }
+
+ public String getCurrentSubtypeMode() {
+ return null != mCurrentSubtype ? mCurrentSubtype.getMode() : KEYBOARD_MODE;
+ }
+
+
// A list of locales which are supported by default for voice input, unless we get a
// different list from Gservices.
private static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES =
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index ced355b..0de474e 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -22,8 +22,13 @@
import android.util.Log;
import android.view.View;
+import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
/**
* This class loads a dictionary and provides a list of suggestions for a given sequence of
@@ -62,40 +67,37 @@
// If you add a type of dictionary, increment DIC_TYPE_LAST_ID
public static final int DIC_TYPE_LAST_ID = 4;
+ public static final String DICT_KEY_MAIN = "main";
+ public static final String DICT_KEY_CONTACTS = "contacts";
+ public static final String DICT_KEY_AUTO = "auto";
+ public static final String DICT_KEY_USER = "user";
+ public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
+ public static final String DICT_KEY_WHITELIST ="whitelist";
+
static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
- private static boolean DBG = LatinImeLogger.sDBG;
+ private static final boolean DBG = LatinImeLogger.sDBG;
+
+ private AutoCorrection mAutoCorrection;
private BinaryDictionary mMainDict;
-
- private Dictionary mUserDictionary;
-
- private Dictionary mAutoDictionary;
-
- private Dictionary mContactsDictionary;
-
- private Dictionary mUserBigramDictionary;
+ private WhitelistDictionary mWhiteListDictionary;
+ private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
+ private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
private int mPrefMaxSuggestions = 12;
private static final int PREF_MAX_BIGRAMS = 60;
- private boolean mAutoTextEnabled;
+ private boolean mQuickFixesEnabled;
private double mAutoCorrectionThreshold;
private int[] mPriorities = new int[mPrefMaxSuggestions];
private int[] mBigramPriorities = new int[PREF_MAX_BIGRAMS];
- // Handle predictive correction for only the first 1280 characters for performance reasons
- // If we support scripts that need latin characters beyond that, we should probably use some
- // kind of a sparse array or language specific list with a mapping lookup table.
- // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of
- // latin characters.
- private int[] mNextLettersFrequencies = new int[1280];
private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>();
ArrayList<CharSequence> mBigramSuggestions = new ArrayList<CharSequence>();
private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>();
- private boolean mHasAutoCorrection;
private String mLowerOriginalWord;
// TODO: Remove these member variables by passing more context to addWord() callback method
@@ -105,7 +107,24 @@
private int mCorrectionMode = CORRECTION_BASIC;
public Suggest(Context context, int dictionaryResId) {
- mMainDict = BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN);
+ init(context, BinaryDictionary.initDictionary(context, dictionaryResId, DIC_MAIN));
+ }
+
+ /* package for test */ Suggest(File dictionary, long startOffset, long length) {
+ init(null, BinaryDictionary.initDictionary(dictionary, startOffset, length, DIC_MAIN));
+ }
+
+ private void init(Context context, BinaryDictionary mainDict) {
+ if (mainDict != null) {
+ mMainDict = mainDict;
+ mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+ mBigramDictionaries.put(DICT_KEY_MAIN, mainDict);
+ }
+ mWhiteListDictionary = WhitelistDictionary.init(context);
+ if (mWhiteListDictionary != null) {
+ mUnigramDictionaries.put(DICT_KEY_WHITELIST, mWhiteListDictionary);
+ }
+ mAutoCorrection = new AutoCorrection();
initPool();
}
@@ -116,8 +135,8 @@
}
}
- public void setAutoTextEnabled(boolean enabled) {
- mAutoTextEnabled = enabled;
+ public void setQuickFixesEnabled(boolean enabled) {
+ mQuickFixesEnabled = enabled;
}
public int getCorrectionMode() {
@@ -132,6 +151,10 @@
return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
}
+ public Map<String, Dictionary> getUnigramDictionaries() {
+ return mUnigramDictionaries;
+ }
+
public int getApproxMaxWordLength() {
return APPROX_MAX_WORD_LENGTH;
}
@@ -141,22 +164,28 @@
* before the main dictionary, if set.
*/
public void setUserDictionary(Dictionary userDictionary) {
- mUserDictionary = userDictionary;
+ if (userDictionary != null)
+ mUnigramDictionaries.put(DICT_KEY_USER, userDictionary);
}
/**
* Sets an optional contacts dictionary resource to be loaded.
*/
- public void setContactsDictionary(Dictionary userDictionary) {
- mContactsDictionary = userDictionary;
+ public void setContactsDictionary(Dictionary contactsDictionary) {
+ if (contactsDictionary != null) {
+ mUnigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+ mBigramDictionaries.put(DICT_KEY_CONTACTS, contactsDictionary);
+ }
}
public void setAutoDictionary(Dictionary autoDictionary) {
- mAutoDictionary = autoDictionary;
+ if (autoDictionary != null)
+ mUnigramDictionaries.put(DICT_KEY_AUTO, autoDictionary);
}
public void setUserBigramDictionary(Dictionary userBigramDictionary) {
- mUserBigramDictionary = userBigramDictionary;
+ if (userBigramDictionary != null)
+ mBigramDictionaries.put(DICT_KEY_USER_BIGRAM, userBigramDictionary);
}
public void setAutoCorrectionThreshold(double threshold) {
@@ -200,16 +229,34 @@
return getSuggestedWordBuilder(view, wordComposer, prevWordForBigram).build();
}
+ private CharSequence capitalizeWord(boolean all, boolean first, CharSequence word) {
+ if (TextUtils.isEmpty(word) || !(all || first)) return word;
+ final int wordLength = word.length();
+ final int poolSize = mStringPool.size();
+ final StringBuilder sb =
+ poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1)
+ : new StringBuilder(getApproxMaxWordLength());
+ sb.setLength(0);
+ if (all) {
+ sb.append(word.toString().toUpperCase());
+ } else if (first) {
+ sb.append(Character.toUpperCase(word.charAt(0)));
+ if (wordLength > 1) {
+ sb.append(word.subSequence(1, wordLength));
+ }
+ }
+ return sb;
+ }
+
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
public SuggestedWords.Builder getSuggestedWordBuilder(View view, WordComposer wordComposer,
CharSequence prevWordForBigram) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mHasAutoCorrection = false;
+ mAutoCorrection.init();
mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
mIsAllUpperCase = wordComposer.isAllUpperCase();
collectGarbage(mSuggestions, mPrefMaxSuggestions);
Arrays.fill(mPriorities, 0);
- Arrays.fill(mNextLettersFrequencies, 0);
// Save a lowercase version of the original word
CharSequence typedWord = wordComposer.getTypedWord();
@@ -235,17 +282,8 @@
if (mMainDict != null && mMainDict.isValidWord(lowerPrevWord)) {
prevWordForBigram = lowerPrevWord;
}
- if (mUserBigramDictionary != null) {
- mUserBigramDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
- }
- if (mMainDict != null) {
- mMainDict.getBigrams(wordComposer, prevWordForBigram, this,
- mNextLettersFrequencies);
+ for (final Dictionary dictionary : mBigramDictionaries.values()) {
+ dictionary.getBigrams(wordComposer, prevWordForBigram, this);
}
char currentChar = wordComposer.getTypedWord().charAt(0);
char currentCharUpper = Character.toUpperCase(currentChar);
@@ -268,97 +306,86 @@
} else if (wordComposer.size() > 1) {
// At second character typed, search the unigrams (scores being affected by bigrams)
- if (mUserDictionary != null || mContactsDictionary != null) {
- if (mUserDictionary != null) {
- mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies);
- }
-
- if (mSuggestions.size() > 0 && isValidWord(typedWord)
- && (mCorrectionMode == CORRECTION_FULL
- || mCorrectionMode == CORRECTION_FULL_BIGRAM)) {
- if (DBG) {
- Log.d(TAG, "Auto corrected by CORRECTION_FULL.");
- }
- mHasAutoCorrection = true;
- }
- }
- if (mMainDict != null) mMainDict.getWords(wordComposer, this, mNextLettersFrequencies);
- if ((mCorrectionMode == CORRECTION_FULL || mCorrectionMode == CORRECTION_FULL_BIGRAM)
- && mSuggestions.size() > 0 && mPriorities.length > 0) {
- // TODO: when the normalized score of the first suggestion is nearly equals to
- // the normalized score of the second suggestion, behave less aggressive.
- final double normalizedScore = Utils.calcNormalizedScore(
- typedWord, mSuggestions.get(0), mPriorities[0]);
- if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Normalized " + typedWord + "," + mSuggestions.get(0) + ","
- + mPriorities[0] + ", " + normalizedScore
- + "(" + mAutoCorrectionThreshold + ")");
- }
- if (normalizedScore >= mAutoCorrectionThreshold) {
- if (DBG) {
- Log.d(TAG, "Auto corrected by S-threthhold.");
- }
- mHasAutoCorrection = true;
- }
+ for (final String key : mUnigramDictionaries.keySet()) {
+ // Skip AutoDictionary and WhitelistDictionary to lookup
+ if (key.equals(DICT_KEY_AUTO) || key.equals(DICT_KEY_WHITELIST))
+ continue;
+ final Dictionary dictionary = mUnigramDictionaries.get(key);
+ dictionary.getWords(wordComposer, this);
}
}
+ CharSequence autoText = null;
+ final String typedWordString = typedWord == null ? null : typedWord.toString();
if (typedWord != null) {
- mSuggestions.add(0, typedWord.toString());
- }
- if (mAutoTextEnabled) {
- int i = 0;
- int max = 6;
- // Don't autotext the suggestions from the dictionaries
- if (mCorrectionMode == CORRECTION_BASIC) max = 1;
- while (i < mSuggestions.size() && i < max) {
- String suggestedWord = mSuggestions.get(i).toString().toLowerCase();
- CharSequence autoText =
- AutoText.get(suggestedWord, 0, suggestedWord.length(), view);
+ // Apply quick fix only for the typed word.
+ if (mQuickFixesEnabled) {
+ final String lowerCaseTypedWord = typedWordString.toLowerCase();
+ CharSequence tempAutoText = capitalizeWord(
+ mIsAllUpperCase, mIsFirstCharCapitalized, AutoText.get(
+ lowerCaseTypedWord, 0, lowerCaseTypedWord.length(), view));
+ // TODO: cleanup canAdd
// Is there an AutoText (also known as Quick Fixes) correction?
- boolean canAdd = autoText != null;
// Capitalize as needed
- final int autoTextLength = autoText != null ? autoText.length() : 0;
- if (autoTextLength > 0 && (mIsAllUpperCase || mIsFirstCharCapitalized)) {
- int poolSize = mStringPool.size();
- StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(
- poolSize - 1) : new StringBuilder(getApproxMaxWordLength());
- sb.setLength(0);
- if (mIsAllUpperCase) {
- sb.append(autoText.toString().toUpperCase());
- } else if (mIsFirstCharCapitalized) {
- sb.append(Character.toUpperCase(autoText.charAt(0)));
- if (autoTextLength > 1) {
- sb.append(autoText.subSequence(1, autoTextLength));
- }
- }
- autoText = sb.toString();
- }
+ boolean canAdd = tempAutoText != null;
// Is that correction already the current prediction (or original word)?
- canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i));
+ canAdd &= !TextUtils.equals(tempAutoText, typedWord);
// Is that correction already the next predicted word?
- if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) {
- canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1));
+ if (canAdd && mSuggestions.size() > 0 && mCorrectionMode != CORRECTION_BASIC) {
+ canAdd &= !TextUtils.equals(tempAutoText, mSuggestions.get(0));
}
if (canAdd) {
if (DBG) {
Log.d(TAG, "Auto corrected by AUTOTEXT.");
}
- mHasAutoCorrection = true;
- mSuggestions.add(i + 1, autoText);
- i++;
+ autoText = tempAutoText;
}
- i++;
}
}
- removeDupes();
- return new SuggestedWords.Builder().addWords(mSuggestions, null);
- }
- public int[] getNextLettersFrequencies() {
- return mNextLettersFrequencies;
+ CharSequence whitelistedWord = capitalizeWord(mIsAllUpperCase, mIsFirstCharCapitalized,
+ mWhiteListDictionary.getWhiteListedWord(typedWordString));
+
+ mAutoCorrection.updateAutoCorrectionStatus(mUnigramDictionaries, wordComposer,
+ mSuggestions, mPriorities, typedWord, mAutoCorrectionThreshold, mCorrectionMode,
+ autoText, whitelistedWord);
+
+ if (autoText != null) {
+ mSuggestions.add(0, autoText);
+ }
+
+ if (whitelistedWord != null) {
+ mSuggestions.add(0, whitelistedWord);
+ }
+
+ if (typedWord != null) {
+ mSuggestions.add(0, typedWordString);
+ }
+ removeDupes();
+
+ if (DBG) {
+ double normalizedScore = mAutoCorrection.getNormalizedScore();
+ ArrayList<SuggestedWords.SuggestedWordInfo> frequencyInfoList =
+ new ArrayList<SuggestedWords.SuggestedWordInfo>();
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("+", false));
+ final int priorityLength = mPriorities.length;
+ for (int i = 0; i < priorityLength; ++i) {
+ if (normalizedScore > 0) {
+ final String priorityThreshold = Integer.toString(mPriorities[i]) + " (" +
+ normalizedScore + ")";
+ frequencyInfoList.add(
+ new SuggestedWords.SuggestedWordInfo(priorityThreshold, false));
+ normalizedScore = 0.0;
+ } else {
+ final String priority = Integer.toString(mPriorities[i]);
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo(priority, false));
+ }
+ }
+ for (int i = priorityLength; i < mSuggestions.size(); ++i) {
+ frequencyInfoList.add(new SuggestedWords.SuggestedWordInfo("--", false));
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, frequencyInfoList);
+ }
+ return new SuggestedWords.Builder().addWords(mSuggestions, null);
}
private void removeDupes() {
@@ -389,15 +416,15 @@
}
public boolean hasAutoCorrection() {
- return mHasAutoCorrection;
+ return mAutoCorrection.hasAutoCorrection();
}
- private boolean compareCaseInsensitive(final String mLowerOriginalWord,
+ private static boolean compareCaseInsensitive(final String lowerOriginalWord,
final char[] word, final int offset, final int length) {
- final int originalLength = mLowerOriginalWord.length();
+ final int originalLength = lowerOriginalWord.length();
if (originalLength == length && Character.isUpperCase(word[offset])) {
for (int i = 0; i < originalLength; i++) {
- if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
+ if (lowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) {
return false;
}
}
@@ -427,7 +454,20 @@
// Check if it's the same word, only caps are different
if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) {
- pos = 0;
+ // TODO: remove this surrounding if clause and move this logic to
+ // getSuggestedWordBuilder.
+ if (suggestions.size() > 0) {
+ final String currentHighestWordLowerCase =
+ suggestions.get(0).toString().toLowerCase();
+ // If the current highest word is also equal to typed word, we need to compare
+ // frequency to determine the insertion position. This does not ensure strictly
+ // correct ordering, but ensures the top score is on top which is enough for
+ // removing duplicates correctly.
+ if (compareCaseInsensitive(currentHighestWordLowerCase, word, offset, length)
+ && freq <= priorities[0]) {
+ pos = 1;
+ }
+ }
} else {
if (dataType == Dictionary.DataType.UNIGRAM) {
// Check if the word was already added before (by bigram data)
@@ -510,16 +550,6 @@
return -1;
}
- public boolean isValidWord(final CharSequence word) {
- if (word == null || word.length() == 0 || mMainDict == null) {
- return false;
- }
- return mMainDict.isValidWord(word)
- || (mUserDictionary != null && mUserDictionary.isValidWord(word))
- || (mAutoDictionary != null && mAutoDictionary.isValidWord(word))
- || (mContactsDictionary != null && mContactsDictionary.isValidWord(word));
- }
-
private void collectGarbage(ArrayList<CharSequence> suggestions, int prefMaxSuggestions) {
int poolSize = mStringPool.size();
int garbageSize = suggestions.size();
@@ -538,25 +568,12 @@
}
public void close() {
- if (mMainDict != null) {
- mMainDict.close();
- mMainDict = null;
+ final Set<Dictionary> dictionaries = new HashSet<Dictionary>();
+ dictionaries.addAll(mUnigramDictionaries.values());
+ dictionaries.addAll(mBigramDictionaries.values());
+ for (final Dictionary dictionary : dictionaries) {
+ dictionary.close();
}
- if (mUserDictionary != null) {
- mUserDictionary.close();
- mUserDictionary = null;
- }
- if (mUserBigramDictionary != null) {
- mUserBigramDictionary.close();
- mUserBigramDictionary = null;
- }
- if (mContactsDictionary != null) {
- mContactsDictionary.close();
- mContactsDictionary = null;
- }
- if (mAutoDictionary != null) {
- mAutoDictionary.close();
- mAutoDictionary = null;
- }
+ mMainDict = null;
}
}
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index f774ce3..fe7aac7 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -24,23 +24,20 @@
import java.util.List;
public class SuggestedWords {
- public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, false, null);
+ public static final SuggestedWords EMPTY = new SuggestedWords(null, false, false, null);
public final List<CharSequence> mWords;
- public final boolean mIsApplicationSpecifiedCompletions;
public final boolean mTypedWordValid;
public final boolean mHasMinimalSuggestion;
public final List<SuggestedWordInfo> mSuggestedWordInfoList;
- private SuggestedWords(List<CharSequence> words, boolean isApplicationSpecifiedCompletions,
- boolean typedWordValid, boolean hasMinamlSuggestion,
- List<SuggestedWordInfo> suggestedWordInfoList) {
+ private SuggestedWords(List<CharSequence> words, boolean typedWordValid,
+ boolean hasMinamlSuggestion, List<SuggestedWordInfo> suggestedWordInfoList) {
if (words != null) {
mWords = words;
} else {
mWords = Collections.emptyList();
}
- mIsApplicationSpecifiedCompletions = isApplicationSpecifiedCompletions;
mTypedWordValid = typedWordValid;
mHasMinimalSuggestion = hasMinamlSuggestion;
mSuggestedWordInfoList = suggestedWordInfoList;
@@ -64,7 +61,6 @@
public static class Builder {
private List<CharSequence> mWords = new ArrayList<CharSequence>();
- private boolean mIsCompletions;
private boolean mTypedWordValid;
private boolean mHasMinimalSuggestion;
private List<SuggestedWordInfo> mSuggestedWordInfoList =
@@ -109,7 +105,6 @@
public Builder setApplicationSpecifiedCompletions(CompletionInfo[] infos) {
for (CompletionInfo info : infos)
addWord(info.getText());
- mIsCompletions = true;
return this;
}
@@ -141,15 +136,14 @@
alreadySeen.add(prevWord);
}
}
- mIsCompletions = false;
mTypedWordValid = false;
mHasMinimalSuggestion = false;
return this;
}
public SuggestedWords build() {
- return new SuggestedWords(mWords, mIsCompletions, mTypedWordValid,
- mHasMinimalSuggestion, mSuggestedWordInfoList);
+ return new SuggestedWords(mWords, mTypedWordValid, mHasMinimalSuggestion,
+ mSuggestedWordInfoList);
}
public int size() {
diff --git a/java/src/com/android/inputmethod/latin/TextEntryState.java b/java/src/com/android/inputmethod/latin/TextEntryState.java
index f571f26..6319643 100644
--- a/java/src/com/android/inputmethod/latin/TextEntryState.java
+++ b/java/src/com/android/inputmethod/latin/TextEntryState.java
@@ -16,117 +16,39 @@
package com.android.inputmethod.latin;
-import com.android.inputmethod.keyboard.Key;
-
-import android.content.Context;
-import android.text.format.DateFormat;
import android.util.Log;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Calendar;
-
public class TextEntryState {
-
- private static final boolean DBG = false;
+ private static final String TAG = TextEntryState.class.getSimpleName();
+ private static final boolean DEBUG = false;
- private static final String TAG = "TextEntryState";
+ private static final int UNKNOWN = 0;
+ private static final int START = 1;
+ private static final int IN_WORD = 2;
+ private static final int ACCEPTED_DEFAULT = 3;
+ private static final int PICKED_SUGGESTION = 4;
+ private static final int PUNCTUATION_AFTER_WORD = 5;
+ private static final int PUNCTUATION_AFTER_ACCEPTED = 6;
+ private static final int SPACE_AFTER_ACCEPTED = 7;
+ private static final int SPACE_AFTER_PICKED = 8;
+ private static final int UNDO_COMMIT = 9;
+ private static final int RECORRECTING = 10;
+ private static final int PICKED_RECORRECTION = 11;
- private static boolean LOGGING = false;
+ private static int sState = UNKNOWN;
+ private static int sPreviousState = UNKNOWN;
- private static int sBackspaceCount = 0;
-
- private static int sAutoSuggestCount = 0;
-
- private static int sAutoSuggestUndoneCount = 0;
-
- private static int sManualSuggestCount = 0;
-
- private static int sWordNotInDictionaryCount = 0;
-
- private static int sSessionCount = 0;
-
- private static int sTypedChars;
-
- private static int sActualChars;
-
- public enum State {
- UNKNOWN,
- START,
- IN_WORD,
- ACCEPTED_DEFAULT,
- PICKED_SUGGESTION,
- PUNCTUATION_AFTER_WORD,
- PUNCTUATION_AFTER_ACCEPTED,
- SPACE_AFTER_ACCEPTED,
- SPACE_AFTER_PICKED,
- UNDO_COMMIT,
- CORRECTING,
- PICKED_CORRECTION,
+ private static void setState(final int newState) {
+ sPreviousState = sState;
+ sState = newState;
}
- private static State sState = State.UNKNOWN;
-
- private static FileOutputStream sKeyLocationFile;
- private static FileOutputStream sUserActionFile;
-
- public static void newSession(Context context) {
- sSessionCount++;
- sAutoSuggestCount = 0;
- sBackspaceCount = 0;
- sAutoSuggestUndoneCount = 0;
- sManualSuggestCount = 0;
- sWordNotInDictionaryCount = 0;
- sTypedChars = 0;
- sActualChars = 0;
- sState = State.START;
-
- if (LOGGING) {
- try {
- sKeyLocationFile = context.openFileOutput("key.txt", Context.MODE_APPEND);
- sUserActionFile = context.openFileOutput("action.txt", Context.MODE_APPEND);
- } catch (IOException ioe) {
- Log.e("TextEntryState", "Couldn't open file for output: " + ioe);
- }
- }
- }
-
- public static void endSession() {
- if (sKeyLocationFile == null) {
- return;
- }
- try {
- sKeyLocationFile.close();
- // Write to log file
- // Write timestamp, settings,
- String out = DateFormat.format("MM:dd hh:mm:ss", Calendar.getInstance().getTime())
- .toString()
- + " BS: " + sBackspaceCount
- + " auto: " + sAutoSuggestCount
- + " manual: " + sManualSuggestCount
- + " typed: " + sWordNotInDictionaryCount
- + " undone: " + sAutoSuggestUndoneCount
- + " saved: " + ((float) (sActualChars - sTypedChars) / sActualChars)
- + "\n";
- sUserActionFile.write(out.getBytes());
- sUserActionFile.close();
- sKeyLocationFile = null;
- sUserActionFile = null;
- } catch (IOException ioe) {
- // ignore
- }
- }
-
public static void acceptedDefault(CharSequence typedWord, CharSequence actualWord) {
if (typedWord == null) return;
- if (!typedWord.equals(actualWord)) {
- sAutoSuggestCount++;
- }
- sTypedChars += typedWord.length();
- sActualChars += actualWord.length();
- sState = State.ACCEPTED_DEFAULT;
+ setState(ACCEPTED_DEFAULT);
LatinImeLogger.logOnAutoSuggestion(typedWord.toString(), actualWord.toString());
- displayState();
+ if (DEBUG)
+ displayState("acceptedDefault", "typedWord", typedWord, "actualWord", actualWord);
}
// State.ACCEPTED_DEFAULT will be changed to other sub-states
@@ -138,151 +60,167 @@
case SPACE_AFTER_ACCEPTED:
case PUNCTUATION_AFTER_ACCEPTED:
case IN_WORD:
- sState = State.ACCEPTED_DEFAULT;
+ setState(ACCEPTED_DEFAULT);
break;
default:
break;
}
- displayState();
+ if (DEBUG) displayState("backToAcceptedDefault", "typedWord", typedWord);
}
- public static void acceptedTyped(@SuppressWarnings("unused") CharSequence typedWord) {
- sWordNotInDictionaryCount++;
- sState = State.PICKED_SUGGESTION;
- displayState();
+ public static void acceptedTyped(CharSequence typedWord) {
+ setState(PICKED_SUGGESTION);
+ if (DEBUG) displayState("acceptedTyped", "typedWord", typedWord);
}
public static void acceptedSuggestion(CharSequence typedWord, CharSequence actualWord) {
- sManualSuggestCount++;
- State oldState = sState;
- if (typedWord.equals(actualWord)) {
- acceptedTyped(typedWord);
- }
- if (oldState == State.CORRECTING || oldState == State.PICKED_CORRECTION) {
- sState = State.PICKED_CORRECTION;
+ if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
+ setState(PICKED_RECORRECTION);
} else {
- sState = State.PICKED_SUGGESTION;
+ setState(PICKED_SUGGESTION);
}
- displayState();
+ if (DEBUG)
+ displayState("acceptedSuggestion", "typedWord", typedWord, "actualWord", actualWord);
}
- public static void selectedForCorrection() {
- sState = State.CORRECTING;
- displayState();
+ public static void selectedForRecorrection() {
+ setState(RECORRECTING);
+ if (DEBUG) displayState("selectedForRecorrection");
}
- public static void onAbortCorrection() {
- if (isCorrecting()) {
- sState = State.START;
+ public static void onAbortRecorrection() {
+ if (sState == RECORRECTING || sState == PICKED_RECORRECTION) {
+ setState(START);
}
- displayState();
+ if (DEBUG) displayState("onAbortRecorrection");
}
public static void typedCharacter(char c, boolean isSeparator) {
- boolean isSpace = c == ' ';
+ final boolean isSpace = (c == ' ');
switch (sState) {
- case IN_WORD:
- if (isSpace || isSeparator) {
- sState = State.START;
- } else {
- // State hasn't changed.
- }
- break;
- case ACCEPTED_DEFAULT:
- case SPACE_AFTER_PICKED:
- if (isSpace) {
- sState = State.SPACE_AFTER_ACCEPTED;
- } else if (isSeparator) {
- sState = State.PUNCTUATION_AFTER_ACCEPTED;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case PICKED_SUGGESTION:
- case PICKED_CORRECTION:
- if (isSpace) {
- sState = State.SPACE_AFTER_PICKED;
- } else if (isSeparator) {
- // Swap
- sState = State.PUNCTUATION_AFTER_ACCEPTED;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case START:
- case UNKNOWN:
- case SPACE_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_ACCEPTED:
- case PUNCTUATION_AFTER_WORD:
- if (!isSpace && !isSeparator) {
- sState = State.IN_WORD;
- } else {
- sState = State.START;
- }
- break;
- case UNDO_COMMIT:
- if (isSpace || isSeparator) {
- sState = State.ACCEPTED_DEFAULT;
- } else {
- sState = State.IN_WORD;
- }
- break;
- case CORRECTING:
- sState = State.START;
- break;
+ case IN_WORD:
+ if (isSpace || isSeparator) {
+ setState(START);
+ } else {
+ // State hasn't changed.
+ }
+ break;
+ case ACCEPTED_DEFAULT:
+ case SPACE_AFTER_PICKED:
+ case PUNCTUATION_AFTER_ACCEPTED:
+ if (isSpace) {
+ setState(SPACE_AFTER_ACCEPTED);
+ } else if (isSeparator) {
+ // Swap
+ setState(PUNCTUATION_AFTER_ACCEPTED);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case PICKED_SUGGESTION:
+ case PICKED_RECORRECTION:
+ if (isSpace) {
+ setState(SPACE_AFTER_PICKED);
+ } else if (isSeparator) {
+ // Swap
+ setState(PUNCTUATION_AFTER_ACCEPTED);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case START:
+ case UNKNOWN:
+ case SPACE_AFTER_ACCEPTED:
+ case PUNCTUATION_AFTER_WORD:
+ if (!isSpace && !isSeparator) {
+ setState(IN_WORD);
+ } else {
+ setState(START);
+ }
+ break;
+ case UNDO_COMMIT:
+ if (isSpace || isSeparator) {
+ setState(ACCEPTED_DEFAULT);
+ } else {
+ setState(IN_WORD);
+ }
+ break;
+ case RECORRECTING:
+ setState(START);
+ break;
}
- displayState();
+ if (DEBUG) displayState("typedCharacter", "char", c, "isSeparator", isSeparator);
}
public static void backspace() {
- if (sState == State.ACCEPTED_DEFAULT) {
- sState = State.UNDO_COMMIT;
- sAutoSuggestUndoneCount++;
+ if (sState == ACCEPTED_DEFAULT) {
+ setState(UNDO_COMMIT);
LatinImeLogger.logOnAutoSuggestionCanceled();
- } else if (sState == State.UNDO_COMMIT) {
- sState = State.IN_WORD;
+ } else if (sState == UNDO_COMMIT) {
+ setState(IN_WORD);
}
- sBackspaceCount++;
- displayState();
+ if (DEBUG) displayState("backspace");
}
public static void reset() {
- sState = State.START;
- displayState();
+ setState(START);
+ if (DEBUG) displayState("reset");
}
- public static State getState() {
- if (DBG) {
- Log.d(TAG, "Returning state = " + sState);
- }
- return sState;
+ public static boolean isAcceptedDefault() {
+ return sState == ACCEPTED_DEFAULT;
}
- public static boolean isCorrecting() {
- return sState == State.CORRECTING || sState == State.PICKED_CORRECTION;
+ public static boolean isSpaceAfterPicked() {
+ return sState == SPACE_AFTER_PICKED;
}
- public static void keyPressedAt(Key key, int x, int y) {
- if (LOGGING && sKeyLocationFile != null && key.mCode >= 32) {
- String out =
- "KEY: " + (char) key.mCode
- + " X: " + x
- + " Y: " + y
- + " MX: " + (key.mX + key.mWidth / 2)
- + " MY: " + (key.mY + key.mHeight / 2)
- + "\n";
- try {
- sKeyLocationFile.write(out.getBytes());
- } catch (IOException ioe) {
- // TODO: May run out of space
- }
+ public static boolean isUndoCommit() {
+ return sState == UNDO_COMMIT;
+ }
+
+ public static boolean isPunctuationAfterAccepted() {
+ return sState == PUNCTUATION_AFTER_ACCEPTED;
+ }
+
+ public static boolean isRecorrecting() {
+ return sState == RECORRECTING || sState == PICKED_RECORRECTION;
+ }
+
+ public static String getState() {
+ return stateName(sState);
+ }
+
+ private static String stateName(int state) {
+ switch (state) {
+ case START: return "START";
+ case IN_WORD: return "IN_WORD";
+ case ACCEPTED_DEFAULT: return "ACCEPTED_DEFAULT";
+ case PICKED_SUGGESTION: return "PICKED_SUGGESTION";
+ case PUNCTUATION_AFTER_WORD: return "PUNCTUATION_AFTER_WORD";
+ case PUNCTUATION_AFTER_ACCEPTED: return "PUNCTUATION_AFTER_ACCEPTED";
+ case SPACE_AFTER_ACCEPTED: return "SPACE_AFTER_ACCEPTED";
+ case SPACE_AFTER_PICKED: return "SPACE_AFTER_PICKED";
+ case UNDO_COMMIT: return "UNDO_COMMIT";
+ case RECORRECTING: return "RECORRECTING";
+ case PICKED_RECORRECTION: return "PICKED_RECORRECTION";
+ default: return "UNKNOWN";
}
}
- private static void displayState() {
- if (DBG) {
- Log.d(TAG, "State = " + sState);
+ private static void displayState(String title, Object ... args) {
+ final StringBuilder sb = new StringBuilder(title);
+ sb.append(':');
+ for (int i = 0; i < args.length; i += 2) {
+ sb.append(' ');
+ sb.append(args[i]);
+ sb.append('=');
+ sb.append(args[i+1].toString());
}
+ sb.append(" state=");
+ sb.append(stateName(sState));
+ sb.append(" previous=");
+ sb.append(stateName(sPreviousState));
+ Log.d(TAG, sb.toString());
}
}
-
diff --git a/java/src/com/android/inputmethod/latin/UserDictionary.java b/java/src/com/android/inputmethod/latin/UserDictionary.java
index 56ee5b9..c06bd73 100644
--- a/java/src/com/android/inputmethod/latin/UserDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserDictionary.java
@@ -126,9 +126,8 @@
}
@Override
- public synchronized void getWords(final WordComposer codes, final WordCallback callback,
- int[] nextLettersFrequencies) {
- super.getWords(codes, callback, nextLettersFrequencies);
+ public synchronized void getWords(final WordComposer codes, final WordCallback callback) {
+ super.getWords(codes, callback);
}
@Override
@@ -138,7 +137,7 @@
private void addWords(Cursor cursor) {
clearDictionary();
-
+ if (cursor == null) return;
final int maxWordLength = getMaxWordLength();
if (cursor.moveToFirst()) {
final int indexWord = cursor.getColumnIndex(Words.WORD);
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index e980d3a..727e3f1 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -16,13 +16,18 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyboardId;
+
+import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.text.InputType;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -41,6 +46,10 @@
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
private static boolean DBG = LatinImeLogger.sDBG;
+ private Utils() {
+ // Intentional empty constructor for utility class.
+ }
+
/**
* Cancel an {@link AsyncTask}.
*
@@ -55,7 +64,7 @@
}
public static class GCUtils {
- private static final String TAG = "GCUtils";
+ private static final String GC_TAG = GCUtils.class.getSimpleName();
public static final int GC_TRY_COUNT = 2;
// GC_TRY_LOOP_MAX is used for the hard limit of GC wait,
// GC_TRY_LOOP_MAX should be greater than GC_TRY_COUNT.
@@ -84,7 +93,7 @@
Thread.sleep(GC_INTERVAL);
return true;
} catch (InterruptedException e) {
- Log.e(TAG, "Sleep was interrupted.");
+ Log.e(GC_TAG, "Sleep was interrupted.");
LatinImeLogger.logOnException(metaData, t);
return false;
}
@@ -261,6 +270,19 @@
return dp[sl][tl];
}
+ // Get the current stack trace
+ public static String getStackTrace() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ throw new RuntimeException();
+ } catch (RuntimeException e) {
+ StackTraceElement[] frames = e.getStackTrace();
+ // Start at 1 because the first frame is here and we don't care about it
+ for (int j = 1; j < frames.length; ++j) sb.append(frames[j].toString() + "\n");
+ }
+ return sb.toString();
+ }
+
// In dictionary.cpp, getSuggestion() method,
// suggestion scores are computed using the below formula.
// original score (called 'frequency')
@@ -268,13 +290,22 @@
// (the number of matched characters between typed word and suggested word))
// * (individual word's score which defined in the unigram dictionary,
// and this score is defined in range [0, 255].)
- // * (when before.length() == after.length(),
- // mFullWordMultiplier (this is defined 2))
- // So, maximum original score is pow(2, before.length()) * 255 * 2
- // So, we can normalize original score by dividing this value.
+ // Then, the following processing is applied.
+ // - If the dictionary word is matched up to the point of the user entry
+ // (full match up to min(before.length(), after.length())
+ // => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2)
+ // - If the word is a true full match except for differences in accents or
+ // capitalization, then treat it as if the frequency was 255.
+ // - If before.length() == after.length()
+ // => multiply by mFullWordMultiplier (this is defined 2))
+ // So, maximum original score is pow(2, min(before.length(), after.length())) * 255 * 2 * 1.2
+ // For historical reasons we ignore the 1.2 modifier (because the measure for a good
+ // autocorrection threshold was done at a time when it didn't exist). This doesn't change
+ // the result.
+ // So, we can normalize original score by dividing pow(2, min(b.l(),a.l())) * 255 * 2.
private static final int MAX_INITIAL_SCORE = 255;
private static final int TYPED_LETTER_MULTIPLIER = 2;
- private static final int FULL_WORD_MULTIPLYER = 2;
+ private static final int FULL_WORD_MULTIPLIER = 2;
public static double calcNormalizedScore(CharSequence before, CharSequence after, int score) {
final int beforeLength = before.length();
final int afterLength = after.length();
@@ -284,7 +315,7 @@
// correction.
final double maximumScore = MAX_INITIAL_SCORE
* Math.pow(TYPED_LETTER_MULTIPLIER, Math.min(beforeLength, afterLength))
- * FULL_WORD_MULTIPLYER;
+ * FULL_WORD_MULTIPLIER;
// add a weight based on edit distance.
// distance <= max(afterLength, beforeLength) == afterLength,
// so, 0 <= distance / afterLength <= 1
@@ -293,7 +324,7 @@
}
public static class UsabilityStudyLogUtils {
- private static final String TAG = "UsabilityStudyLogUtils";
+ private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
private static final String FILENAME = "log.txt";
private static final UsabilityStudyLogUtils sInstance =
new UsabilityStudyLogUtils();
@@ -330,7 +361,7 @@
try {
mWriter = getPrintWriter(mDirectory, FILENAME, false);
} catch (IOException e) {
- Log.e(TAG, "Can't create log file.");
+ Log.e(USABILITY_TAG, "Can't create log file.");
}
}
}
@@ -367,7 +398,7 @@
final String printString = String.format("%s\t%d\t%s\n",
mDateFormat.format(mDate), currentTime, log);
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Write: " + log);
+ Log.d(USABILITY_TAG, "Write: " + log);
}
mWriter.print(printString);
}
@@ -388,10 +419,10 @@
sb.append(line);
}
} catch (IOException e) {
- Log.e(TAG, "Can't read log file.");
+ Log.e(USABILITY_TAG, "Can't read log file.");
} finally {
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "output all logs\n" + sb.toString());
+ Log.d(USABILITY_TAG, "output all logs\n" + sb.toString());
}
mIms.getCurrentInputConnection().commitText(sb.toString(), 0);
try {
@@ -410,7 +441,7 @@
public void run() {
if (mFile != null && mFile.exists()) {
if (LatinImeLogger.sDBG) {
- Log.d(TAG, "Delete log file.");
+ Log.d(USABILITY_TAG, "Delete log file.");
}
mFile.delete();
mWriter.close();
@@ -439,4 +470,95 @@
return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
}
}
+
+ public static int getKeyboardMode(EditorInfo attribute) {
+ if (attribute == null)
+ return KeyboardId.MODE_TEXT;
+
+ final int inputType = attribute.inputType;
+ final int variation = inputType & InputType.TYPE_MASK_VARIATION;
+
+ switch (inputType & InputType.TYPE_MASK_CLASS) {
+ case InputType.TYPE_CLASS_NUMBER:
+ case InputType.TYPE_CLASS_DATETIME:
+ return KeyboardId.MODE_NUMBER;
+ case InputType.TYPE_CLASS_PHONE:
+ return KeyboardId.MODE_PHONE;
+ case InputType.TYPE_CLASS_TEXT:
+ if (Utils.isEmailVariation(variation)) {
+ return KeyboardId.MODE_EMAIL;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_URI) {
+ return KeyboardId.MODE_URL;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
+ return KeyboardId.MODE_IM;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_FILTER) {
+ return KeyboardId.MODE_TEXT;
+ } else if (variation == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
+ return KeyboardId.MODE_WEB;
+ } else {
+ return KeyboardId.MODE_TEXT;
+ }
+ default:
+ return KeyboardId.MODE_TEXT;
+ }
+ }
+
+ public static boolean isEmailVariation(int variation) {
+ return variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+ || variation == InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS;
+ }
+
+ // Please refer to TextView.isPasswordInputType
+ public static boolean isPasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD))
+ || (variation
+ == (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
+ }
+
+ // Please refer to TextView.isVisiblePasswordInputType
+ public static boolean isVisiblePasswordInputType(int inputType) {
+ final int variation =
+ inputType & (InputType.TYPE_MASK_CLASS | InputType.TYPE_MASK_VARIATION);
+ return variation
+ == (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
+ }
+
+ public static boolean containsInCsv(String key, String csv) {
+ if (csv == null)
+ return false;
+ for (String option : csv.split(",")) {
+ if (option.equals(key))
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean inPrivateImeOptions(String packageName, String key,
+ EditorInfo attribute) {
+ if (attribute == null)
+ return false;
+ return containsInCsv(packageName != null ? packageName + "." + key : key,
+ attribute.privateImeOptions);
+ }
+
+ /**
+ * Returns a main dictionary resource id
+ * @return main dictionary resource id
+ */
+ public static int getMainDictionaryResourceId(Resources res) {
+ return res.getIdentifier("main", "raw", LatinIME.class.getPackage().getName());
+ }
+
+ public static void loadNativeLibrary() {
+ try {
+ System.loadLibrary("jni_latinime");
+ } catch (UnsatisfiedLinkError ule) {
+ Log.e(TAG, "Could not load native library jni_latinime");
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/WhitelistDictionary.java b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
new file mode 100644
index 0000000..2389d4e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/WhitelistDictionary.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.HashMap;
+
+public class WhitelistDictionary extends Dictionary {
+
+ private static final boolean DBG = LatinImeLogger.sDBG;
+ private static final String TAG = WhitelistDictionary.class.getSimpleName();
+
+ private final HashMap<String, Pair<Integer, String>> mWhitelistWords =
+ new HashMap<String, Pair<Integer, String>>();
+
+ private static final WhitelistDictionary sInstance = new WhitelistDictionary();
+
+ private WhitelistDictionary() {
+ }
+
+ public static WhitelistDictionary init(Context context) {
+ synchronized (sInstance) {
+ if (context != null) {
+ sInstance.initWordlist(
+ context.getResources().getStringArray(R.array.wordlist_whitelist));
+ } else {
+ sInstance.mWhitelistWords.clear();
+ }
+ }
+ return sInstance;
+ }
+
+ private void initWordlist(String[] wordlist) {
+ mWhitelistWords.clear();
+ final int N = wordlist.length;
+ if (N % 3 != 0) {
+ if (DBG) {
+ Log.d(TAG, "The number of the whitelist is invalid.");
+ }
+ return;
+ }
+ try {
+ for (int i = 0; i < N; i += 3) {
+ final int score = Integer.valueOf(wordlist[i]);
+ final String before = wordlist[i + 1];
+ final String after = wordlist[i + 2];
+ if (before != null && after != null) {
+ mWhitelistWords.put(
+ before.toLowerCase(), new Pair<Integer, String>(score, after));
+ }
+ }
+ } catch (NumberFormatException e) {
+ if (DBG) {
+ Log.d(TAG, "The score of the word is invalid.");
+ }
+ }
+ }
+
+ public String getWhiteListedWord(String before) {
+ if (before == null) return null;
+ final String lowerCaseBefore = before.toLowerCase();
+ if(mWhitelistWords.containsKey(lowerCaseBefore)) {
+ if (DBG) {
+ Log.d(TAG, "--- found whiteListedWord: " + lowerCaseBefore);
+ }
+ return mWhitelistWords.get(lowerCaseBefore).second;
+ }
+ return null;
+ }
+
+ // Not used for WhitelistDictionary. We use getWhitelistedWord() in Suggest.java instead
+ @Override
+ public void getWords(WordComposer composer, WordCallback callback) {
+ }
+
+ @Override
+ public boolean isValidWord(CharSequence word) {
+ if (TextUtils.isEmpty(word)) return false;
+ return !TextUtils.isEmpty(getWhiteListedWord(word.toString()));
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 2e415b7..0258389 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,22 +16,32 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.KeyDetector;
+
import java.util.ArrayList;
/**
* A place to store the currently composing word with information such as adjacent key codes as well
*/
public class WordComposer {
+
+ public static final int NOT_A_CODE = KeyDetector.NOT_A_CODE;
+ public static final int NOT_A_COORDINATE = -1;
+
/**
* The list of unicode values for each keystroke (including surrounding keys)
*/
private final ArrayList<int[]> mCodes;
-
+
+ private int mTypedLength;
+ private final int[] mXCoordinates;
+ private final int[] mYCoordinates;
+
/**
* The word chosen from the candidate list, until it is committed.
*/
private String mPreferredWord;
-
+
private final StringBuilder mTypedWord;
private int mCapsCount;
@@ -44,17 +54,24 @@
private boolean mIsFirstCharCapitalized;
public WordComposer() {
- mCodes = new ArrayList<int[]>(12);
- mTypedWord = new StringBuilder(20);
+ final int N = BinaryDictionary.MAX_WORD_LENGTH;
+ mCodes = new ArrayList<int[]>(N);
+ mTypedWord = new StringBuilder(N);
+ mTypedLength = 0;
+ mXCoordinates = new int[N];
+ mYCoordinates = new int[N];
}
- WordComposer(WordComposer copy) {
- mCodes = new ArrayList<int[]>(copy.mCodes);
- mPreferredWord = copy.mPreferredWord;
- mTypedWord = new StringBuilder(copy.mTypedWord);
- mCapsCount = copy.mCapsCount;
- mAutoCapitalized = copy.mAutoCapitalized;
- mIsFirstCharCapitalized = copy.mIsFirstCharCapitalized;
+ WordComposer(WordComposer source) {
+ mCodes = new ArrayList<int[]>(source.mCodes);
+ mPreferredWord = source.mPreferredWord;
+ mTypedWord = new StringBuilder(source.mTypedWord);
+ mCapsCount = source.mCapsCount;
+ mAutoCapitalized = source.mAutoCapitalized;
+ mIsFirstCharCapitalized = source.mIsFirstCharCapitalized;
+ mTypedLength = source.mTypedLength;
+ mXCoordinates = source.mXCoordinates;
+ mYCoordinates = source.mYCoordinates;
}
/**
@@ -62,6 +79,7 @@
*/
public void reset() {
mCodes.clear();
+ mTypedLength = 0;
mIsFirstCharCapitalized = false;
mPreferredWord = null;
mTypedWord.setLength(0);
@@ -85,15 +103,28 @@
return mCodes.get(index);
}
+ public int[] getXCoordinates() {
+ return mXCoordinates;
+ }
+
+ public int[] getYCoordinates() {
+ return mYCoordinates;
+ }
+
/**
* Add a new keystroke, with codes[0] containing the pressed key's unicode and the rest of
* the array containing unicode for adjacent keys, sorted by reducing probability/proximity.
* @param codes the array of unicode values
*/
- public void add(int primaryCode, int[] codes) {
+ public void add(int primaryCode, int[] codes, int x, int y) {
mTypedWord.append((char) primaryCode);
correctPrimaryJuxtapos(primaryCode, codes);
mCodes.add(codes);
+ if (mTypedLength < BinaryDictionary.MAX_WORD_LENGTH) {
+ mXCoordinates[mTypedLength] = x;
+ mYCoordinates[mTypedLength] = y;
+ }
+ ++mTypedLength;
if (Character.isUpperCase((char) primaryCode)) mCapsCount++;
}
@@ -124,6 +155,9 @@
mTypedWord.deleteCharAt(lastPos);
if (Character.isUpperCase(last)) mCapsCount--;
}
+ if (mTypedLength > 0) {
+ --mTypedLength;
+ }
}
/**
diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
index 61a194a..105656f 100644
--- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
+++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java
@@ -25,6 +25,7 @@
import com.android.inputmethod.latin.SharedPreferencesCompat;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.SuggestedWords;
+import com.android.inputmethod.latin.Utils;
import android.app.AlertDialog;
import android.content.Context;
@@ -38,16 +39,13 @@
import android.preference.PreferenceManager;
import android.provider.Browser;
import android.speech.SpeechRecognizer;
-import android.text.Layout;
-import android.text.Selection;
-import android.text.Spannable;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.LayoutInflater;
-import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -77,15 +75,10 @@
// For example, the user has a Chinese UI but activates voice input.
private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE =
"has_used_voice_input_unsupported_locale";
- // The private IME option used to indicate that no microphone should be shown for a
- // given text field. For instance this is specified by the search dialog when the
- // dialog is already showing a voice search button.
- private static final String IME_OPTION_NO_MICROPHONE = "nm";
private static final int RECOGNITIONVIEW_HEIGHT_THRESHOLD_RATIO = 6;
- @SuppressWarnings("unused")
- private static final String TAG = "VoiceIMEConnector";
- private static boolean DEBUG = LatinImeLogger.sDBG;
+ private static final String TAG = VoiceIMEConnector.class.getSimpleName();
+ private static final boolean DEBUG = LatinImeLogger.sDBG;
private boolean mAfterVoiceInput;
private boolean mHasUsedVoiceInput;
@@ -177,7 +170,7 @@
if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(mService);
+ AlertDialog.Builder builder = new UrlLinkAlertDialogBuilder(mService);
builder.setCancelable(true);
builder.setIcon(R.drawable.ic_mic_dialog);
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -215,90 +208,80 @@
mService.getText(R.string.voice_warning_how_to_turn_off));
}
builder.setMessage(message);
-
builder.setTitle(R.string.voice_warning_title);
mVoiceWarningDialog = builder.create();
- Window window = mVoiceWarningDialog.getWindow();
- WindowManager.LayoutParams lp = window.getAttributes();
+ final Window window = mVoiceWarningDialog.getWindow();
+ final WindowManager.LayoutParams lp = window.getAttributes();
lp.token = token;
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
window.setAttributes(lp);
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
mVoiceInput.logKeyboardWarningDialogShown();
mVoiceWarningDialog.show();
- // Make URL in the dialog message clickable
- TextView textView = (TextView) mVoiceWarningDialog.findViewById(android.R.id.message);
- if (textView != null) {
- final CustomLinkMovementMethod method = CustomLinkMovementMethod.getInstance();
- method.setVoiceWarningDialog(mVoiceWarningDialog);
- textView.setMovementMethod(method);
- }
}
- private static class CustomLinkMovementMethod extends LinkMovementMethod {
- private static CustomLinkMovementMethod sLinkMovementMethodInstance =
- new CustomLinkMovementMethod();
+ private static class UrlLinkAlertDialogBuilder extends AlertDialog.Builder {
private AlertDialog mAlertDialog;
- public void setVoiceWarningDialog(AlertDialog alertDialog) {
- mAlertDialog = alertDialog;
+ public UrlLinkAlertDialogBuilder(Context context) {
+ super(context);
}
- public static CustomLinkMovementMethod getInstance() {
- return sLinkMovementMethodInstance;
- }
-
- // Almost the same as LinkMovementMethod.onTouchEvent(), but overrides it for
- // FLAG_ACTIVITY_NEW_TASK and mAlertDialog.cancel().
@Override
- public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
- int action = event.getAction();
+ public AlertDialog.Builder setMessage(CharSequence message) {
+ return super.setMessage(replaceURLSpan(message));
+ }
- if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
-
- ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- if (link[0] instanceof URLSpan) {
- URLSpan urlSpan = (URLSpan) link[0];
- Uri uri = Uri.parse(urlSpan.getURL());
- Context context = widget.getContext();
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
- if (mAlertDialog != null) {
- // Go back to the previous IME for now.
- // TODO: If we can find a way to bring the new activity to front
- // while keeping the warning dialog, we don't need to cancel here.
- mAlertDialog.cancel();
- }
- context.startActivity(intent);
- } else {
- link[0].onClick(widget);
- }
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
- return true;
- } else {
- Selection.removeSelection(buffer);
- }
+ private Spanned replaceURLSpan(CharSequence message) {
+ // Replace all spans with the custom span
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(message);
+ for (URLSpan span : ssb.getSpans(0, ssb.length(), URLSpan.class)) {
+ int spanStart = ssb.getSpanStart(span);
+ int spanEnd = ssb.getSpanEnd(span);
+ int spanFlags = ssb.getSpanFlags(span);
+ ssb.removeSpan(span);
+ ssb.setSpan(new ClickableSpan(span.getURL()), spanStart, spanEnd, spanFlags);
}
- return super.onTouchEvent(widget, buffer, event);
+ return ssb;
+ }
+
+ @Override
+ public AlertDialog create() {
+ final AlertDialog dialog = super.create();
+
+ dialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialogInterface) {
+ // Make URL in the dialog message click-able.
+ TextView textView = (TextView) mAlertDialog.findViewById(android.R.id.message);
+ if (textView != null) {
+ textView.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ }
+ });
+ mAlertDialog = dialog;
+ return dialog;
+ }
+
+ class ClickableSpan extends URLSpan {
+ public ClickableSpan(String url) {
+ super(url);
+ }
+
+ @Override
+ public void onClick(View widget) {
+ Uri uri = Uri.parse(getURL());
+ Context context = widget.getContext();
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ // Add this flag to start an activity from service
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
+ // Dismiss the warning dialog and go back to the previous IME.
+ // TODO: If we can find a way to bring the new activity to front while keeping
+ // the warning dialog, we don't need to dismiss it here.
+ mAlertDialog.cancel();
+ context.startActivity(intent);
+ }
}
}
@@ -641,9 +624,11 @@
}
private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
- return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext)
- && !(attribute != null
- && IME_OPTION_NO_MICROPHONE.equals(attribute.privateImeOptions))
+ final boolean noMic = Utils.inPrivateImeOptions(null,
+ LatinIME.IME_OPTION_NO_MICROPHONE_COMPAT, attribute)
+ || Utils.inPrivateImeOptions(mService.getPackageName(),
+ LatinIME.IME_OPTION_NO_MICROPHONE, attribute);
+ return ENABLE_VOICE_BUTTON && fieldCanDoVoice(fieldContext) && !noMic
&& SpeechRecognizer.isRecognitionAvailable(mService);
}
@@ -729,7 +714,7 @@
mHandler.updateVoiceResults();
}
- public FieldContext makeFieldContext() {
+ private FieldContext makeFieldContext() {
SubtypeSwitcher switcher = SubtypeSwitcher.getInstance();
return new FieldContext(mService.getCurrentInputConnection(),
mService.getCurrentInputEditorInfo(), switcher.getInputLocaleStr(),
diff --git a/native/Android.mk b/native/Android.mk
index a8fe06d..c8342e3 100644
--- a/native/Android.mk
+++ b/native/Android.mk
@@ -4,10 +4,13 @@
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src
LOCAL_SRC_FILES := \
+ jni/com_android_inputmethod_keyboard_ProximityInfo.cpp \
jni/com_android_inputmethod_latin_BinaryDictionary.cpp \
+ jni/onload.cpp \
src/bigram_dictionary.cpp \
src/char_utils.cpp \
src/dictionary.cpp \
+ src/proximity_info.cpp \
src/unigram_dictionary.cpp
#FLAG_DBG := true
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
new file mode 100644
index 0000000..3db89ed
--- /dev/null
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -0,0 +1,90 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "LatinIME: jni: ProximityInfo"
+
+#include "com_android_inputmethod_keyboard_ProximityInfo.h"
+#include "jni.h"
+#include "proximity_info.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+
+// ----------------------------------------------------------------------------
+
+namespace latinime {
+
+//
+// helper function to throw an exception
+//
+static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) {
+ if (jclass cls = env->FindClass(ex)) {
+ char msg[1000];
+ snprintf(msg, sizeof(msg), fmt, data);
+ env->ThrowNew(cls, msg);
+ env->DeleteLocalRef(cls);
+ }
+}
+
+static jint latinime_Keyboard_setProximityInfo(JNIEnv *env, jobject object,
+ jint maxProximityCharsSize, jint displayWidth, jint displayHeight, jint gridWidth,
+ jint gridHeight, jintArray proximityCharsArray) {
+ jint* proximityChars = env->GetIntArrayElements(proximityCharsArray, NULL);
+ ProximityInfo *proximityInfo = new ProximityInfo(maxProximityCharsSize, displayWidth,
+ displayHeight, gridWidth, gridHeight, (const uint32_t *)proximityChars);
+ env->ReleaseIntArrayElements(proximityCharsArray, proximityChars, 0);
+ return (jint)proximityInfo;
+}
+
+static void latinime_Keyboard_release(JNIEnv *env, jobject object, jint proximityInfo) {
+ ProximityInfo *pi = (ProximityInfo*)proximityInfo;
+ if (!pi) return;
+ delete pi;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod sKeyboardMethods[] = {
+ {"setProximityInfoNative", "(IIIII[I)I", (void*)latinime_Keyboard_setProximityInfo},
+ {"releaseProximityInfoNative", "(I)V", (void*)latinime_Keyboard_release}
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className, JNINativeMethod* gMethods,
+ int numMethods) {
+ jclass clazz;
+
+ clazz = env->FindClass(className);
+ if (clazz == NULL) {
+ LOGE("Native registration unable to find class '%s'", className);
+ return JNI_FALSE;
+ }
+ if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+ LOGE("RegisterNatives failed for '%s'", className);
+ return JNI_FALSE;
+ }
+
+ return JNI_TRUE;
+}
+
+int register_ProximityInfo(JNIEnv *env) {
+ const char* const kClassPathName = "com/android/inputmethod/keyboard/ProximityInfo";
+ return registerNativeMethods(env, kClassPathName, sKeyboardMethods,
+ sizeof(sKeyboardMethods) / sizeof(sKeyboardMethods[0]));
+}
+
+}; // namespace latinime
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h
new file mode 100644
index 0000000..bdeeb8f
--- /dev/null
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.h
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H
+#define _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H
+
+#include "jni.h"
+
+namespace latinime {
+int register_ProximityInfo(JNIEnv *env);
+}
+
+#endif // _COM_ANDROID_INPUTMETHOD_KEYBOARD_PROXIMITYINFO_H
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 25580f4..555a522 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -15,10 +15,12 @@
** limitations under the License.
*/
-#define LOG_TAG "LatinIME: jni"
+#define LOG_TAG "LatinIME: jni: BinaryDictionary"
+#include "com_android_inputmethod_latin_BinaryDictionary.h"
#include "dictionary.h"
#include "jni.h"
+#include "proximity_info.h"
#include <assert.h>
#include <errno.h>
@@ -35,7 +37,7 @@
// ----------------------------------------------------------------------------
-using namespace latinime;
+namespace latinime {
//
// helper function to throw an exception
@@ -43,7 +45,7 @@
static void throwException(JNIEnv *env, const char* ex, const char* fmt, int data) {
if (jclass cls = env->FindClass(ex)) {
char msg[1000];
- sprintf(msg, fmt, data);
+ snprintf(msg, sizeof(msg), fmt, data);
env->ThrowNew(cls, msg);
env->DeleteLocalRef(cls);
}
@@ -123,26 +125,29 @@
}
static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jint dict,
- jintArray inputArray, jint arraySize, jcharArray outputArray, jintArray frequencyArray,
- jintArray nextLettersArray, jint nextLettersSize) {
+ jint proximityInfo, jintArray xCoordinatesArray, jintArray yCoordinatesArray,
+ jintArray inputArray, jint arraySize, jint flags,
+ jcharArray outputArray, jintArray frequencyArray) {
Dictionary *dictionary = (Dictionary*)dict;
if (!dictionary) return 0;
+ ProximityInfo *pInfo = (ProximityInfo*)proximityInfo;
+ if (!pInfo) return 0;
+
+ int *xCoordinates = env->GetIntArrayElements(xCoordinatesArray, NULL);
+ int *yCoordinates = env->GetIntArrayElements(yCoordinatesArray, NULL);
int *frequencies = env->GetIntArrayElements(frequencyArray, NULL);
int *inputCodes = env->GetIntArrayElements(inputArray, NULL);
jchar *outputChars = env->GetCharArrayElements(outputArray, NULL);
- int *nextLetters = nextLettersArray != NULL ? env->GetIntArrayElements(nextLettersArray, NULL)
- : NULL;
- int count = dictionary->getSuggestions(inputCodes, arraySize, (unsigned short*) outputChars,
- frequencies, nextLetters, nextLettersSize);
+ int count = dictionary->getSuggestions(pInfo, xCoordinates, yCoordinates, inputCodes,
+ arraySize, flags, (unsigned short*) outputChars, frequencies);
env->ReleaseIntArrayElements(frequencyArray, frequencies, 0);
env->ReleaseIntArrayElements(inputArray, inputCodes, JNI_ABORT);
+ env->ReleaseIntArrayElements(xCoordinatesArray, xCoordinates, 0);
+ env->ReleaseIntArrayElements(yCoordinatesArray, yCoordinates, 0);
env->ReleaseCharArrayElements(outputArray, outputChars, 0);
- if (nextLetters) {
- env->ReleaseIntArrayElements(nextLettersArray, nextLetters, 0);
- }
return count;
}
@@ -206,10 +211,10 @@
// ----------------------------------------------------------------------------
-static JNINativeMethod gMethods[] = {
+static JNINativeMethod sMethods[] = {
{"openNative", "(Ljava/lang/String;JJIIIII)I", (void*)latinime_BinaryDictionary_open},
{"closeNative", "(I)V", (void*)latinime_BinaryDictionary_close},
- {"getSuggestionsNative", "(I[II[C[I[II)I", (void*)latinime_BinaryDictionary_getSuggestions},
+ {"getSuggestionsNative", "(II[I[I[III[C[I)I", (void*)latinime_BinaryDictionary_getSuggestions},
{"isValidWordNative", "(I[CI)Z", (void*)latinime_BinaryDictionary_isValidWord},
{"getBigramsNative", "(I[CI[II[C[IIII)I", (void*)latinime_BinaryDictionary_getBigrams}
};
@@ -231,33 +236,10 @@
return JNI_TRUE;
}
-static int registerNatives(JNIEnv *env) {
+int register_BinaryDictionary(JNIEnv *env) {
const char* const kClassPathName = "com/android/inputmethod/latin/BinaryDictionary";
- return registerNativeMethods(env, kClassPathName, gMethods,
- sizeof(gMethods) / sizeof(gMethods[0]));
+ return registerNativeMethods(env, kClassPathName, sMethods,
+ sizeof(sMethods) / sizeof(sMethods[0]));
}
-/*
- * Returns the JNI version on success, -1 on failure.
- */
-jint JNI_OnLoad(JavaVM* vm, void* reserved) {
- JNIEnv* env = NULL;
- jint result = -1;
-
- if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
- LOGE("ERROR: GetEnv failed");
- goto bail;
- }
- assert(env != NULL);
-
- if (!registerNatives(env)) {
- LOGE("ERROR: BinaryDictionary native registration failed");
- goto bail;
- }
-
- /* success -- return valid version number */
- result = JNI_VERSION_1_4;
-
-bail:
- return result;
-}
+}; // namespace latinime
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.h b/native/jni/com_android_inputmethod_latin_BinaryDictionary.h
new file mode 100644
index 0000000..f7cd81f
--- /dev/null
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.h
@@ -0,0 +1,27 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H
+#define _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H
+
+#include "jni.h"
+
+namespace latinime {
+int register_BinaryDictionary(JNIEnv *env);
+}
+
+#endif // _COM_ANDROID_INPUTMETHOD_LATIN_BINARYDICTIONARY_H
diff --git a/native/jni/onload.cpp b/native/jni/onload.cpp
new file mode 100644
index 0000000..f02c9a0
--- /dev/null
+++ b/native/jni/onload.cpp
@@ -0,0 +1,62 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#define LOG_TAG "LatinIME: jni"
+
+#include "com_android_inputmethod_keyboard_ProximityInfo.h"
+#include "com_android_inputmethod_latin_BinaryDictionary.h"
+#include "jni.h"
+#include "proximity_info.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+
+// ----------------------------------------------------------------------------
+
+using namespace latinime;
+
+
+/*
+ * Returns the JNI version on success, -1 on failure.
+ */
+jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+ JNIEnv* env = NULL;
+ jint result = -1;
+
+ if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
+ LOGE("ERROR: GetEnv failed");
+ goto bail;
+ }
+ assert(env != NULL);
+
+ if (!register_BinaryDictionary(env)) {
+ LOGE("ERROR: BinaryDictionary native registration failed");
+ goto bail;
+ }
+
+ if (!register_ProximityInfo(env)) {
+ LOGE("ERROR: ProximityInfo native registration failed");
+ goto bail;
+ }
+
+ /* success -- return valid version number */
+ result = JNI_VERSION_1_4;
+
+bail:
+ return result;
+}
diff --git a/native/src/debug.h b/native/src/debug.h
new file mode 100644
index 0000000..ae629b2
--- /dev/null
+++ b/native/src/debug.h
@@ -0,0 +1,69 @@
+/*
+**
+** Copyright 2011, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef LATINIME_DEBUG_H
+#define LATINIME_DEBUG_H
+
+#include "defines.h"
+
+static inline unsigned char* convertToUnibyteString(unsigned short* input, unsigned char* output,
+ const unsigned int length) {
+ int i = 0;
+ for (; i <= length && input[i] != 0; ++i)
+ output[i] = input[i] & 0xFF;
+ output[i] = 0;
+ return output;
+}
+static inline unsigned char* convertToUnibyteStringAndReplaceLastChar(unsigned short* input,
+ unsigned char* output, const unsigned int length, unsigned char c) {
+ int i = 0;
+ for (; i <= length && input[i] != 0; ++i)
+ output[i] = input[i] & 0xFF;
+ output[i-1] = c;
+ output[i] = 0;
+ return output;
+}
+static inline void LOGI_S16(unsigned short* string, const unsigned int length) {
+ unsigned char tmp_buffer[length];
+ convertToUnibyteString(string, tmp_buffer, length);
+ LOGI(">> %s", tmp_buffer);
+ // The log facility is throwing out log that comes too fast. The following
+ // is a dirty way of slowing down processing so that we can see all log.
+ // TODO : refactor this in a blocking log or something.
+ // usleep(10);
+}
+static inline void LOGI_S16_PLUS(unsigned short* string, const unsigned int length,
+ unsigned char c) {
+ unsigned char tmp_buffer[length+1];
+ convertToUnibyteStringAndReplaceLastChar(string, tmp_buffer, length, c);
+ LOGI(">> %s", tmp_buffer);
+ // Likewise
+ // usleep(10);
+}
+
+static inline void printDebug(const char* tag, int* codes, int codesSize, int MAX_PROXIMITY_CHARS) {
+ unsigned char *buf = (unsigned char*)malloc((1 + codesSize) * sizeof(*buf));
+
+ buf[codesSize] = 0;
+ while (--codesSize >= 0)
+ buf[codesSize] = (unsigned char)codes[codesSize * MAX_PROXIMITY_CHARS];
+ LOGI("%s, WORD = %s", tag, buf);
+
+ free(buf);
+}
+
+#endif // LATINIME_DEBUG_H
diff --git a/native/src/defines.h b/native/src/defines.h
index c1eaf0d..00cbb6c 100644
--- a/native/src/defines.h
+++ b/native/src/defines.h
@@ -28,6 +28,7 @@
#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
// Profiler
#include <time.h>
@@ -83,6 +84,7 @@
#define DEBUG_SHOW_FOUND_WORD false
#define DEBUG_NODE false
#define DEBUG_TRACE false
+#define DEBUG_PROXIMITY_INFO false
#define PROF_BUF_SIZE 0
#define PROF_RESET
@@ -100,6 +102,9 @@
#ifndef U_SHORT_MAX
#define U_SHORT_MAX 1 << 16
#endif
+#ifndef S_INT_MAX
+#define S_INT_MAX 2147483647 // ((1 << 31) - 1)
+#endif
// Define this to use mmap() for dictionary loading. Undefine to use malloc() instead of mmap().
// We measured and compared performance of both, and found mmap() is fairly good in terms of
@@ -124,33 +129,40 @@
#define DICTIONARY_HEADER_SIZE 2
#define NOT_VALID_WORD -99
+#define KEYCODE_SPACE ' '
+
#define SUGGEST_WORDS_WITH_MISSING_CHARACTER true
#define SUGGEST_WORDS_WITH_MISSING_SPACE_CHARACTER true
#define SUGGEST_WORDS_WITH_EXCESSIVE_CHARACTER true
#define SUGGEST_WORDS_WITH_TRANSPOSED_CHARACTERS true
+#define SUGGEST_WORDS_WITH_SPACE_PROXIMITY true
// The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
-#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 75
+#define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 70
#define WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE 80
#define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75
#define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75
#define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
#define FULL_MATCHED_WORDS_PROMOTION_RATE 120
-// This is used as a bare multiplier (not subject to /100)
-#define FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER 2
-
// 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.
#define MAX_WORD_LENGTH_INTERNAL 48
#define MAX_DEPTH_MULTIPLIER 3
+// TODO: Reduce this constant if possible; check the maximum number of umlauts in the same German
+// word in the dictionary
+#define DEFAULT_MAX_UMLAUT_SEARCH_DEPTH 5
+
// Minimum suggest depth for one word for all cases except for missing space suggestions.
#define MIN_SUGGEST_DEPTH 1
#define MIN_USER_TYPED_LENGTH_FOR_MISSING_SPACE_SUGGESTION 3
#define MIN_USER_TYPED_LENGTH_FOR_EXCESSIVE_CHARACTER_SUGGESTION 3
+// The size of next letters frequency array. Zero will disable the feature.
+#define NEXT_LETTERS_SIZE 0
+
#define min(a,b) ((a)<(b)?(a):(b))
#endif // LATINIME_DEFINES_H
diff --git a/native/src/dictionary.cpp b/native/src/dictionary.cpp
index fe33757..d69cb2a 100644
--- a/native/src/dictionary.cpp
+++ b/native/src/dictionary.cpp
@@ -23,6 +23,7 @@
namespace latinime {
+// TODO: Change the type of all keyCodes to uint32_t
Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust,
int typedLetterMultiplier, int fullWordMultiplier,
int maxWordLength, int maxWords, int maxAlternatives)
@@ -53,8 +54,7 @@
}
// TODO: use uint16_t instead of unsigned short
-bool Dictionary::isValidWord(unsigned short *word, int length)
-{
+bool Dictionary::isValidWord(unsigned short *word, int length) {
if (IS_LATEST_DICT_VERSION) {
return (isValidWordRec(DICTIONARY_HEADER_SIZE, word, 0, length) != NOT_VALID_WORD);
} else {
diff --git a/native/src/dictionary.h b/native/src/dictionary.h
index cef1cf9..13b2a28 100644
--- a/native/src/dictionary.h
+++ b/native/src/dictionary.h
@@ -19,6 +19,7 @@
#include "bigram_dictionary.h"
#include "defines.h"
+#include "proximity_info.h"
#include "unigram_dictionary.h"
namespace latinime {
@@ -27,10 +28,10 @@
public:
Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler,
int fullWordMultiplier, int maxWordLength, int maxWords, int maxAlternatives);
- int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
- int *nextLetters, int nextLettersSize) {
- return mUnigramDictionary->getSuggestions(codes, codesSize, outWords, frequencies,
- nextLetters, nextLettersSize);
+ int getSuggestions(ProximityInfo *proximityInfo, int *xcoordinates, int *ycoordinates,
+ int *codes, int codesSize, int flags, unsigned short *outWords, int *frequencies) {
+ return mUnigramDictionary->getSuggestions(proximityInfo, xcoordinates, ycoordinates, codes,
+ codesSize, flags, outWords, frequencies);
}
// TODO: Call mBigramDictionary instead of mUnigramDictionary
@@ -40,6 +41,7 @@
return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies,
maxWordLength, maxBigrams, maxAlternatives);
}
+
bool isValidWord(unsigned short *word, int length);
int isValidWordRec(int pos, unsigned short *word, int offset, int length);
void *getDict() { return (void *)mDict; }
diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp
new file mode 100644
index 0000000..102123c
--- /dev/null
+++ b/native/src/proximity_info.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#define LOG_TAG "LatinIME: proximity_info.cpp"
+
+#include "proximity_info.h"
+
+namespace latinime {
+ProximityInfo::ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
+ const int keyboardHeight, const int gridWidth, const int gridHeight,
+ const uint32_t *proximityCharsArray)
+ : MAX_PROXIMITY_CHARS_SIZE(maxProximityCharsSize), KEYBOARD_WIDTH(keyboardWidth),
+ KEYBOARD_HEIGHT(keyboardHeight), GRID_WIDTH(gridWidth), GRID_HEIGHT(gridHeight),
+ CELL_WIDTH((keyboardWidth + gridWidth - 1) / gridWidth),
+ CELL_HEIGHT((keyboardHeight + gridHeight - 1) / gridHeight) {
+ const int len = GRID_WIDTH * GRID_HEIGHT * MAX_PROXIMITY_CHARS_SIZE;
+ mProximityCharsArray = new uint32_t[len];
+ if (DEBUG_PROXIMITY_INFO) {
+ LOGI("Create proximity info array %d", len);
+ }
+ memcpy(mProximityCharsArray, proximityCharsArray, len * sizeof(mProximityCharsArray[0]));
+}
+
+ProximityInfo::~ProximityInfo() {
+ delete[] mProximityCharsArray;
+}
+
+inline int ProximityInfo::getStartIndexFromCoordinates(const int x, const int y) const {
+ return ((y / CELL_HEIGHT) * GRID_WIDTH + (x / CELL_WIDTH))
+ * MAX_PROXIMITY_CHARS_SIZE;
+}
+
+bool ProximityInfo::hasSpaceProximity(const int x, const int y) const {
+ const int startIndex = getStartIndexFromCoordinates(x, y);
+ if (DEBUG_PROXIMITY_INFO) {
+ LOGI("hasSpaceProximity: index %d", startIndex);
+ }
+ for (int i = 0; i < MAX_PROXIMITY_CHARS_SIZE; ++i) {
+ if (DEBUG_PROXIMITY_INFO) {
+ LOGI("Index: %d", mProximityCharsArray[startIndex + i]);
+ }
+ if (mProximityCharsArray[startIndex + i] == KEYCODE_SPACE) {
+ return true;
+ }
+ }
+ return false;
+}
+} // namespace latinime
diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h
new file mode 100644
index 0000000..0f12018
--- /dev/null
+++ b/native/src/proximity_info.h
@@ -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.
+ */
+
+#ifndef LATINIME_PROXIMITY_INFO_H
+#define LATINIME_PROXIMITY_INFO_H
+
+#include <stdint.h>
+
+#include "defines.h"
+
+namespace latinime {
+
+class ProximityInfo {
+public:
+ ProximityInfo(const int maxProximityCharsSize, const int keyboardWidth,
+ const int keybaordHeight, const int gridWidth, const int gridHeight,
+ const uint32_t *proximityCharsArray);
+ ~ProximityInfo();
+ bool hasSpaceProximity(const int x, const int y) const;
+private:
+ int getStartIndexFromCoordinates(const int x, const int y) const;
+ const int CELL_WIDTH;
+ const int CELL_HEIGHT;
+ const int KEYBOARD_WIDTH;
+ const int KEYBOARD_HEIGHT;
+ const int GRID_WIDTH;
+ const int GRID_HEIGHT;
+ const int MAX_PROXIMITY_CHARS_SIZE;
+ uint32_t *mProximityCharsArray;
+};
+}; // namespace latinime
+#endif // LATINIME_PROXIMITY_INFO_H
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index dfbe822..30fbaea 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -29,20 +29,144 @@
namespace latinime {
+const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] =
+ { { 'a', 'e' },
+ { 'o', 'e' },
+ { 'u', 'e' } };
+
UnigramDictionary::UnigramDictionary(const unsigned char *dict, int typedLetterMultiplier,
int fullWordMultiplier, int maxWordLength, int maxWords, int maxProximityChars,
const bool isLatestDictVersion)
- : DICT(dict), MAX_WORD_LENGTH(maxWordLength),MAX_WORDS(maxWords),
+ : DICT(dict), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords),
MAX_PROXIMITY_CHARS(maxProximityChars), IS_LATEST_DICT_VERSION(isLatestDictVersion),
TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier),
- ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0) {
+ ROOT_POS(isLatestDictVersion ? DICTIONARY_HEADER_SIZE : 0),
+ BYTES_IN_ONE_CHAR(MAX_PROXIMITY_CHARS * sizeof(*mInputCodes)),
+ MAX_UMLAUT_SEARCH_DEPTH(DEFAULT_MAX_UMLAUT_SEARCH_DEPTH) {
if (DEBUG_DICT) LOGI("UnigramDictionary - constructor");
}
UnigramDictionary::~UnigramDictionary() {}
-int UnigramDictionary::getSuggestions(int *codes, int codesSize, unsigned short *outWords,
- int *frequencies, int *nextLetters, int nextLettersSize) {
+static inline unsigned int getCodesBufferSize(const int* codes, const int codesSize,
+ const int MAX_PROXIMITY_CHARS) {
+ return sizeof(*codes) * MAX_PROXIMITY_CHARS * codesSize;
+}
+
+bool UnigramDictionary::isDigraph(const int* codes, const int i, const int codesSize) const {
+
+ // There can't be a digraph if we don't have at least 2 characters to examine
+ if (i + 2 > codesSize) return false;
+
+ // Search for the first char of some digraph
+ int lastDigraphIndex = -1;
+ const int thisChar = codes[i * MAX_PROXIMITY_CHARS];
+ for (lastDigraphIndex = sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0]) - 1;
+ lastDigraphIndex >= 0; --lastDigraphIndex) {
+ if (thisChar == GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].first) break;
+ }
+ // No match: return early
+ if (lastDigraphIndex < 0) return false;
+
+ // It's an interesting digraph if the second char matches too.
+ return GERMAN_UMLAUT_DIGRAPHS[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS];
+}
+
+// Mostly the same arguments as the non-recursive version, except:
+// codes is the original value. It points to the start of the work buffer, and gets passed as is.
+// codesSize is the size of the user input (thus, it is the size of codesSrc).
+// codesDest is the current point in the work buffer.
+// codesSrc is the current point in the user-input, original, content-unmodified buffer.
+// codesRemain is the remaining size in codesSrc.
+void UnigramDictionary::getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo,
+ const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
+ const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
+ const int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies) {
+
+ if (currentDepth < MAX_UMLAUT_SEARCH_DEPTH) {
+ for (int i = 0; i < codesRemain; ++i) {
+ if (isDigraph(codesSrc, i, codesRemain)) {
+ // Found a digraph. We will try both spellings. eg. the word is "pruefen"
+
+ // Copy the word up to the first char of the digraph, then continue processing
+ // on the remaining part of the word, skipping the second char of the digraph.
+ // In our example, copy "pru" and continue running on "fen"
+ // Make i the index of the second char of the digraph for simplicity. Forgetting
+ // to do that results in an infinite recursion so take care!
+ ++i;
+ memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
+ getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+ codesBuffer, codesBufferSize, flags,
+ codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
+ currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS, outWords,
+ frequencies);
+
+ // Copy the second char of the digraph in place, then continue processing on
+ // the remaining part of the word.
+ // In our example, after "pru" in the buffer copy the "e", and continue on "fen"
+ memcpy(codesDest + i * MAX_PROXIMITY_CHARS, codesSrc + i * MAX_PROXIMITY_CHARS,
+ BYTES_IN_ONE_CHAR);
+ getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
+ codesBuffer, codesBufferSize, flags, codesSrc + i * MAX_PROXIMITY_CHARS,
+ codesRemain - i, currentDepth + 1, codesDest + i * MAX_PROXIMITY_CHARS,
+ outWords, frequencies);
+ return;
+ }
+ }
+ }
+
+ // If we come here, we hit the end of the word: let's check it against the dictionary.
+ // In our example, we'll come here once for "prufen" and then once for "pruefen".
+ // If the word contains several digraphs, we'll come it for the product of them.
+ // eg. if the word is "ueberpruefen" we'll test, in order, against
+ // "uberprufen", "uberpruefen", "ueberprufen", "ueberpruefen".
+ const unsigned int remainingBytes = BYTES_IN_ONE_CHAR * codesRemain;
+ if (0 != remainingBytes)
+ memcpy(codesDest, codesSrc, remainingBytes);
+
+ getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
+ (codesDest - codesBuffer) / MAX_PROXIMITY_CHARS + codesRemain, outWords, frequencies);
+}
+
+int UnigramDictionary::getSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates,
+ const int *ycoordinates, const int *codes, const int codesSize, const int flags,
+ unsigned short *outWords, int *frequencies) {
+
+ if (REQUIRES_GERMAN_UMLAUT_PROCESSING & flags)
+ { // Incrementally tune the word and try all possibilities
+ int codesBuffer[getCodesBufferSize(codes, codesSize, MAX_PROXIMITY_CHARS)];
+ getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer,
+ codesSize, flags, codes, codesSize, 0, codesBuffer, outWords, frequencies);
+ } else { // Normal processing
+ getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize,
+ outWords, frequencies);
+ }
+
+ PROF_START(20);
+ // Get the word count
+ int suggestedWordsCount = 0;
+ while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
+ suggestedWordsCount++;
+ }
+
+ if (DEBUG_DICT) {
+ LOGI("Returning %d words", suggestedWordsCount);
+ LOGI("Next letters: ");
+ for (int k = 0; k < NEXT_LETTERS_SIZE; k++) {
+ if (mNextLettersFrequency[k] > 0) {
+ LOGI("%c = %d,", k, mNextLettersFrequency[k]);
+ }
+ }
+ }
+ PROF_END(20);
+ PROF_CLOSE;
+ return suggestedWordsCount;
+}
+
+void UnigramDictionary::getWordSuggestions(const ProximityInfo *proximityInfo,
+ const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize,
+ unsigned short *outWords, int *frequencies) {
+
PROF_OPEN;
PROF_START(0);
initSuggestions(codes, codesSize, outWords, frequencies);
@@ -52,7 +176,7 @@
PROF_END(0);
PROF_START(1);
- getSuggestionCandidates(-1, -1, -1, nextLetters, nextLettersSize, MAX_DEPTH);
+ getSuggestionCandidates(-1, -1, -1, mNextLettersFrequency, NEXT_LETTERS_SIZE, MAX_DEPTH);
PROF_END(1);
PROF_START(2);
@@ -99,28 +223,25 @@
PROF_END(5);
PROF_START(6);
- // Get the word count
- int suggestedWordsCount = 0;
- while (suggestedWordsCount < MAX_WORDS && mFrequencies[suggestedWordsCount] > 0) {
- suggestedWordsCount++;
- }
-
- if (DEBUG_DICT) {
- LOGI("Returning %d words", suggestedWordsCount);
- LOGI("Next letters: ");
- for (int k = 0; k < nextLettersSize; k++) {
- if (nextLetters[k] > 0) {
- LOGI("%c = %d,", k, nextLetters[k]);
+ if (SUGGEST_WORDS_WITH_SPACE_PROXIMITY) {
+ // The first and last "mistyped spaces" are taken care of by excessive character handling
+ for (int i = 1; i < codesSize - 1; ++i) {
+ if (DEBUG_DICT) LOGI("--- Suggest words with proximity space %d", i);
+ const int x = xcoordinates[i];
+ const int y = ycoordinates[i];
+ if (DEBUG_PROXIMITY_INFO)
+ LOGI("Input[%d] x = %d, y = %d, has space proximity = %d",
+ i, x, y, proximityInfo->hasSpaceProximity(x, y));
+ if (proximityInfo->hasSpaceProximity(x, y)) {
+ getMistypedSpaceWords(mInputLength, i);
}
}
}
PROF_END(6);
- PROF_CLOSE;
- return suggestedWordsCount;
}
-void UnigramDictionary::initSuggestions(int *codes, int codesSize, unsigned short *outWords,
- int *frequencies) {
+void UnigramDictionary::initSuggestions(const int *codes, const int codesSize,
+ unsigned short *outWords, int *frequencies) {
if (DEBUG_DICT) LOGI("initSuggest");
mFrequencies = frequencies;
mOutputChars = outWords;
@@ -182,7 +303,7 @@
return false;
}
-unsigned short UnigramDictionary::toLowerCase(unsigned short c) {
+unsigned short UnigramDictionary::toBaseLowerCase(unsigned short c) {
if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) {
c = BASE_CHARS[c];
}
@@ -198,7 +319,7 @@
if (length != mInputLength) {
return false;
}
- int *inputCodes = mInputCodes;
+ const int *inputCodes = mInputCodes;
while (length--) {
if ((unsigned int) *inputCodes != (unsigned int) *word) {
return false;
@@ -238,7 +359,7 @@
if (mStackChildCount[depth] > 0) {
--mStackChildCount[depth];
bool traverseAllNodes = mStackTraverseAll[depth];
- int snr = mStackNodeFreq[depth];
+ int matchWeight = mStackNodeFreq[depth];
int inputIndex = mStackInputIndex[depth];
int diffs = mStackDiffs[depth];
int siblingPos = mStackSiblingPos[depth];
@@ -246,9 +367,10 @@
// depth will never be greater than maxDepth because in that case,
// needsToTraverseChildrenNodes should be false
const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, depth,
- maxDepth, traverseAllNodes, snr, inputIndex, diffs, skipPos, excessivePos,
- transposedPos, nextLetters, nextLettersSize, &childCount, &firstChildPos,
- &traverseAllNodes, &snr, &inputIndex, &diffs, &siblingPos);
+ maxDepth, traverseAllNodes, matchWeight, inputIndex, diffs, skipPos,
+ excessivePos, transposedPos, nextLetters, nextLettersSize, &childCount,
+ &firstChildPos, &traverseAllNodes, &matchWeight, &inputIndex, &diffs,
+ &siblingPos);
// Update next sibling pos
mStackSiblingPos[depth] = siblingPos;
if (needsToTraverseChildrenNodes) {
@@ -256,7 +378,7 @@
++depth;
mStackChildCount[depth] = childCount;
mStackTraverseAll[depth] = traverseAllNodes;
- mStackNodeFreq[depth] = snr;
+ mStackNodeFreq[depth] = matchWeight;
mStackInputIndex[depth] = inputIndex;
mStackDiffs[depth] = diffs;
mStackSiblingPos[depth] = firstChildPos;
@@ -276,27 +398,31 @@
}
}
-bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
- if (missingSpacePos <= 0 || missingSpacePos >= inputLength
- || inputLength >= MAX_WORD_LENGTH) return false;
- const int newWordLength = inputLength + 1;
+bool UnigramDictionary::getSplitTwoWordsSuggestion(const int inputLength,
+ const int firstWordStartPos, const int firstWordLength, const int secondWordStartPos,
+ const int secondWordLength) {
+ if (inputLength >= MAX_WORD_LENGTH) return false;
+ if (0 >= firstWordLength || 0 >= secondWordLength || firstWordStartPos >= secondWordStartPos
+ || firstWordStartPos < 0 || secondWordStartPos + secondWordLength > inputLength)
+ return false;
+ const int newWordLength = firstWordLength + secondWordLength + 1;
// Allocating variable length array on stack
unsigned short word[newWordLength];
- const int firstFreq = getBestWordFreq(0, missingSpacePos, mWord);
+ const int firstFreq = getBestWordFreq(firstWordStartPos, firstWordLength, mWord);
if (DEBUG_DICT) LOGI("First freq: %d", firstFreq);
if (firstFreq <= 0) return false;
- for (int i = 0; i < missingSpacePos; ++i) {
+ for (int i = 0; i < firstWordLength; ++i) {
word[i] = mWord[i];
}
- const int secondFreq = getBestWordFreq(missingSpacePos, inputLength - missingSpacePos, mWord);
+ const int secondFreq = getBestWordFreq(secondWordStartPos, secondWordLength, mWord);
if (DEBUG_DICT) LOGI("Second freq: %d", secondFreq);
if (secondFreq <= 0) return false;
- word[missingSpacePos] = SPACE;
- for (int i = (missingSpacePos + 1); i < newWordLength; ++i) {
- word[i] = mWord[i - missingSpacePos - 1];
+ word[firstWordLength] = SPACE;
+ for (int i = (firstWordLength + 1); i < newWordLength; ++i) {
+ word[i] = mWord[i - firstWordLength - 1];
}
int pairFreq = ((firstFreq + secondFreq) / 2);
@@ -306,6 +432,17 @@
return true;
}
+bool UnigramDictionary::getMissingSpaceWords(const int inputLength, const int missingSpacePos) {
+ return getSplitTwoWordsSuggestion(
+ inputLength, 0, missingSpacePos, missingSpacePos, inputLength - missingSpacePos);
+}
+
+bool UnigramDictionary::getMistypedSpaceWords(const int inputLength, const int spaceProximityPos) {
+ return getSplitTwoWordsSuggestion(
+ inputLength, 0, spaceProximityPos, spaceProximityPos + 1,
+ inputLength - spaceProximityPos - 1);
+}
+
// Keep this for comparing spec to new getWords
void UnigramDictionary::getWordsOld(const int initialPos, const int inputLength, const int skipPos,
const int excessivePos, const int transposedPos,int *nextLetters,
@@ -319,40 +456,52 @@
}
void UnigramDictionary::getWordsRec(const int childrenCount, const int pos, const int depth,
- const int maxDepth, const bool traverseAllNodes, const int snr, const int inputIndex,
- const int diffs, const int skipPos, const int excessivePos, const int transposedPos,
- int *nextLetters, const int nextLettersSize) {
+ const int maxDepth, const bool traverseAllNodes, const int matchWeight,
+ const int inputIndex, const int diffs, const int skipPos, const int excessivePos,
+ const int transposedPos, int *nextLetters, const int nextLettersSize) {
int siblingPos = pos;
for (int i = 0; i < childrenCount; ++i) {
int newCount;
int newChildPosition;
const int newDepth = depth + 1;
bool newTraverseAllNodes;
- int newSnr;
+ int newMatchRate;
int newInputIndex;
int newDiffs;
int newSiblingPos;
const bool needsToTraverseChildrenNodes = processCurrentNode(siblingPos, depth, maxDepth,
- traverseAllNodes, snr, inputIndex, diffs, skipPos, excessivePos, transposedPos,
+ traverseAllNodes, matchWeight, inputIndex, diffs,
+ skipPos, excessivePos, transposedPos,
nextLetters, nextLettersSize,
- &newCount, &newChildPosition, &newTraverseAllNodes, &newSnr,
+ &newCount, &newChildPosition, &newTraverseAllNodes, &newMatchRate,
&newInputIndex, &newDiffs, &newSiblingPos);
siblingPos = newSiblingPos;
if (needsToTraverseChildrenNodes) {
getWordsRec(newCount, newChildPosition, newDepth, maxDepth, newTraverseAllNodes,
- newSnr, newInputIndex, newDiffs, skipPos, excessivePos, transposedPos,
+ newMatchRate, newInputIndex, newDiffs, skipPos, excessivePos, transposedPos,
nextLetters, nextLettersSize);
}
}
}
+static const int TWO_31ST_DIV_255 = S_INT_MAX / 255;
+static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) {
+ return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX);
+}
inline int UnigramDictionary::calculateFinalFreq(const int inputIndex, const int depth,
- const int snr, const int skipPos, const int excessivePos, const int transposedPos,
- const int freq, const bool sameLength) {
+ const int matchWeight, const int skipPos, const int excessivePos, const int transposedPos,
+ const int freq, const bool sameLength) const {
// TODO: Demote by edit distance
- int finalFreq = freq * snr;
- if (skipPos >= 0) multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE, &finalFreq);
+ int finalFreq = freq * matchWeight;
+ if (skipPos >= 0) {
+ if (mInputLength >= 3) {
+ multiplyRate(WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE *
+ (mInputLength - 2) / (mInputLength - 1), &finalFreq);
+ } else {
+ finalFreq = 0;
+ }
+ }
if (transposedPos >= 0) multiplyRate(
WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq);
if (excessivePos >= 0) {
@@ -363,24 +512,24 @@
}
int lengthFreq = TYPED_LETTER_MULTIPLIER;
for (int i = 0; i < depth; ++i) lengthFreq *= TYPED_LETTER_MULTIPLIER;
- if (lengthFreq == snr) {
+ if (lengthFreq == matchWeight) {
if (depth > 1) {
if (DEBUG_DICT) LOGI("Found full matched word.");
multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq);
}
if (sameLength && transposedPos < 0 && skipPos < 0 && excessivePos < 0) {
- finalFreq *= FULL_MATCH_ACCENTS_OR_CAPITALIZATION_DIFFER_MULTIPLIER;
+ finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq);
}
}
- if (sameLength && skipPos < 0) finalFreq *= FULL_WORD_MULTIPLIER;
+ if (sameLength) finalFreq *= FULL_WORD_MULTIPLIER;
return finalFreq;
}
inline void UnigramDictionary::onTerminalWhenUserTypedLengthIsGreaterThanInputLength(
- unsigned short *word, const int inputIndex, const int depth, const int snr,
+ unsigned short *word, const int inputIndex, const int depth, const int matchWeight,
int *nextLetters, const int nextLettersSize, const int skipPos, const int excessivePos,
const int transposedPos, const int freq) {
- const int finalFreq = calculateFinalFreq(inputIndex, depth, snr, skipPos, excessivePos,
+ const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight, skipPos, excessivePos,
transposedPos, freq, false);
if (depth >= MIN_SUGGEST_DEPTH) addWord(word, depth + 1, finalFreq);
if (depth >= mInputLength && skipPos < 0) {
@@ -389,10 +538,10 @@
}
inline void UnigramDictionary::onTerminalWhenUserTypedLengthIsSameAsInputLength(
- unsigned short *word, const int inputIndex, const int depth, const int snr,
+ unsigned short *word, const int inputIndex, const int depth, const int matchWeight,
const int skipPos, const int excessivePos, const int transposedPos, const int freq) {
if (sameAsTyped(word, depth + 1)) return;
- const int finalFreq = calculateFinalFreq(inputIndex, depth, snr, skipPos,
+ const int finalFreq = calculateFinalFreq(inputIndex, depth, matchWeight, skipPos,
excessivePos, transposedPos, freq, true);
// Proximity collection will promote a word of the same length as what user typed.
if (depth >= MIN_SUGGEST_DEPTH) addWord(word, depth + 1, finalFreq);
@@ -400,18 +549,18 @@
inline bool UnigramDictionary::needsToSkipCurrentNode(const unsigned short c,
const int inputIndex, const int skipPos, const int depth) {
- const unsigned short userTypedChar = (mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS))[0];
+ const unsigned short userTypedChar = getInputCharsAt(inputIndex)[0];
// Skip the ' or other letter and continue deeper
return (c == QUOTE && userTypedChar != QUOTE) || skipPos == depth;
}
inline bool UnigramDictionary::existsAdjacentProximityChars(const int inputIndex,
- const int inputLength) {
+ const int inputLength) const {
if (inputIndex < 0 || inputIndex >= inputLength) return false;
const int currentChar = *getInputCharsAt(inputIndex);
const int leftIndex = inputIndex - 1;
if (leftIndex >= 0) {
- int *leftChars = getInputCharsAt(leftIndex);
+ const int *leftChars = getInputCharsAt(leftIndex);
int i = 0;
while (leftChars[i] > 0 && i < MAX_PROXIMITY_CHARS) {
if (leftChars[i++] == currentChar) return true;
@@ -419,7 +568,7 @@
}
const int rightIndex = inputIndex + 1;
if (rightIndex < inputLength) {
- int *rightChars = getInputCharsAt(rightIndex);
+ const int *rightChars = getInputCharsAt(rightIndex);
int i = 0;
while (rightChars[i] > 0 && i < MAX_PROXIMITY_CHARS) {
if (rightChars[i++] == currentChar) return true;
@@ -428,32 +577,54 @@
return false;
}
+
+// In the following function, c is the current character of the dictionary word
+// currently examined.
+// currentChars is an array containing the keys close to the character the
+// user actually typed at the same position. We want to see if c is in it: if so,
+// then the word contains at that position a character close to what the user
+// typed.
+// What the user typed is actually the first character of the array.
+// Notice : accented characters do not have a proximity list, so they are alone
+// in their list. The non-accented version of the character should be considered
+// "close", but not the other keys close to the non-accented version.
inline UnigramDictionary::ProximityType UnigramDictionary::getMatchedProximityId(
const int *currentChars, const unsigned short c, const int skipPos,
const int excessivePos, const int transposedPos) {
- const unsigned short lowerC = toLowerCase(c);
- int j = 0;
+ const unsigned short baseLowerC = toBaseLowerCase(c);
+
+ // The first char in the array is what user typed. If it matches right away,
+ // that means the user typed that same char for this pos.
+ if (currentChars[0] == baseLowerC || currentChars[0] == c)
+ return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR;
+
+ // If one of those is true, we should not check for close characters at all.
+ if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0)
+ return UNRELATED_CHAR;
+
+ // If the non-accented, lowercased version of that first character matches c,
+ // then we have a non-accented version of the accented character the user
+ // typed. Treat it as a close char.
+ if (toBaseLowerCase(currentChars[0]) == baseLowerC)
+ return NEAR_PROXIMITY_CHAR;
+
+ // Not an exact nor an accent-alike match: search the list of close keys
+ int j = 1;
while (currentChars[j] > 0 && j < MAX_PROXIMITY_CHARS) {
- const bool matched = (currentChars[j] == lowerC || currentChars[j] == c);
- // If skipPos is defined, not to search proximity collections.
- // First char is what user typed.
- if (matched) {
- if (j > 0) return NEAR_PROXIMITY_CHAR;
- return SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR;
- } else if (skipPos >= 0 || excessivePos >= 0 || transposedPos >= 0) {
- // Not to check proximity characters
- return UNRELATED_CHAR;
- }
+ const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c);
+ if (matched) return NEAR_PROXIMITY_CHAR;
++j;
}
+
+ // Was not included, signal this as an unrelated character.
return UNRELATED_CHAR;
}
inline bool UnigramDictionary::processCurrentNode(const int pos, const int depth,
- const int maxDepth, const bool traverseAllNodes, int snr, int inputIndex,
+ const int maxDepth, const bool traverseAllNodes, int matchWeight, int inputIndex,
const int diffs, const int skipPos, const int excessivePos, const int transposedPos,
int *nextLetters, const int nextLettersSize, int *newCount, int *newChildPosition,
- bool *newTraverseAllNodes, int *newSnr, int*newInputIndex, int *newDiffs,
+ bool *newTraverseAllNodes, int *newMatchRate, int *newInputIndex, int *newDiffs,
int *nextSiblingPosition) {
if (DEBUG_DICT) {
int inputCount = 0;
@@ -480,15 +651,16 @@
mWord[depth] = c;
if (traverseAllNodes && terminal) {
onTerminalWhenUserTypedLengthIsGreaterThanInputLength(mWord, inputIndex, depth,
- snr, nextLetters, nextLettersSize, skipPos, excessivePos, transposedPos, freq);
+ matchWeight, nextLetters, nextLettersSize, skipPos, excessivePos, transposedPos,
+ freq);
}
if (!needsToTraverseChildrenNodes) return false;
*newTraverseAllNodes = traverseAllNodes;
- *newSnr = snr;
+ *newMatchRate = matchWeight;
*newDiffs = diffs;
*newInputIndex = inputIndex;
} else {
- int *currentChars = mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS);
+ const int *currentChars = getInputCharsAt(inputIndex);
if (transposedPos >= 0) {
if (inputIndex == transposedPos) currentChars += MAX_PROXIMITY_CHARS;
@@ -502,18 +674,18 @@
// If inputIndex is greater than mInputLength, that means there is no
// proximity chars. So, we don't need to check proximity.
if (SAME_OR_ACCENTED_OR_CAPITALIZED_CHAR == matchedProximityCharId) {
- snr = snr * TYPED_LETTER_MULTIPLIER;
+ matchWeight = matchWeight * TYPED_LETTER_MULTIPLIER;
}
bool isSameAsUserTypedLength = mInputLength == inputIndex + 1
|| (excessivePos == mInputLength - 1 && inputIndex == mInputLength - 2);
if (isSameAsUserTypedLength && terminal) {
- onTerminalWhenUserTypedLengthIsSameAsInputLength(mWord, inputIndex, depth, snr,
+ onTerminalWhenUserTypedLengthIsSameAsInputLength(mWord, inputIndex, depth, matchWeight,
skipPos, excessivePos, transposedPos, freq);
}
if (!needsToTraverseChildrenNodes) return false;
// Start traversing all nodes after the index exceeds the user typed length
*newTraverseAllNodes = isSameAsUserTypedLength;
- *newSnr = snr;
+ *newMatchRate = matchWeight;
*newDiffs = diffs + ((NEAR_PROXIMITY_CHAR == matchedProximityCharId) ? 1 : 0);
*newInputIndex = inputIndex + 1;
}
@@ -591,14 +763,14 @@
const int startInputIndex, const int depth, unsigned short *word, int *newChildPosition,
int *newCount, bool *newTerminal, int *newFreq, int *siblingPos) {
const int inputIndex = startInputIndex + depth;
- const int *currentChars = mInputCodes + (inputIndex * MAX_PROXIMITY_CHARS);
+ const int *currentChars = getInputCharsAt(inputIndex);
unsigned short c;
*siblingPos = Dictionary::setDictionaryValues(DICT, IS_LATEST_DICT_VERSION, firstChildPos, &c,
newChildPosition, newTerminal, newFreq);
const unsigned int inputC = currentChars[0];
if (DEBUG_DICT) assert(inputC <= U_SHORT_MAX);
- const unsigned short lowerC = toLowerCase(c);
- const bool matched = (inputC == lowerC || inputC == c);
+ const unsigned short baseLowerC = toBaseLowerCase(c);
+ const bool matched = (inputC == baseLowerC || inputC == c);
const bool hasChild = *newChildPosition != 0;
if (matched) {
word[depth] = c;
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 90c9814..3d3007c 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -18,6 +18,7 @@
#define LATINIME_UNIGRAM_DICTIONARY_H
#include "defines.h"
+#include "proximity_info.h"
namespace latinime {
@@ -32,12 +33,22 @@
public:
UnigramDictionary(const unsigned char *dict, int typedLetterMultipler, int fullWordMultiplier,
int maxWordLength, int maxWords, int maxProximityChars, const bool isLatestDictVersion);
- int getSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies,
- int *nextLetters, int nextLettersSize);
+ int getSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates,
+ const int *ycoordinates, const int *codes, const int codesSize, const int flags,
+ unsigned short *outWords, int *frequencies);
~UnigramDictionary();
private:
- void initSuggestions(int *codes, int codesSize, unsigned short *outWords, int *frequencies);
+ void getWordSuggestions(const ProximityInfo *proximityInfo, const int *xcoordinates,
+ const int *ycoordinates, const int *codes, const int codesSize,
+ unsigned short *outWords, int *frequencies);
+ bool isDigraph(const int* codes, const int i, const int codesSize) const;
+ void getWordWithDigraphSuggestionsRec(const ProximityInfo *proximityInfo,
+ const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
+ const int codesBufferSize, const int flags, const int* codesSrc, const int codesRemain,
+ const int currentDepth, int* codesDest, unsigned short* outWords, int* frequencies);
+ void initSuggestions(const int *codes, const int codesSize, unsigned short *outWords,
+ int *frequencies);
void getSuggestionCandidates(const int skipPos, const int excessivePos,
const int transposedPos, int *nextLetters, const int nextLettersSize,
const int maxDepth);
@@ -48,19 +59,24 @@
int wideStrLen(unsigned short *str);
bool sameAsTyped(unsigned short *word, int length);
bool addWord(unsigned short *word, int length, int frequency);
- unsigned short toLowerCase(unsigned short c);
+ unsigned short toBaseLowerCase(unsigned short c);
void getWordsRec(const int childrenCount, const int pos, const int depth, const int maxDepth,
const bool traverseAllNodes, const int snr, const int inputIndex, const int diffs,
const int skipPos, const int excessivePos, const int transposedPos, int *nextLetters,
const int nextLettersSize);
+ bool getSplitTwoWordsSuggestion(const int inputLength,
+ const int firstWordStartPos, const int firstWordLength,
+ const int secondWordStartPos, const int secondWordLength);
bool getMissingSpaceWords(const int inputLength, const int missingSpacePos);
+ bool getMistypedSpaceWords(const int inputLength, const int spaceProximityPos);
// Keep getWordsOld for comparing performance between getWords and getWordsOld
void getWordsOld(const int initialPos, const int inputLength, const int skipPos,
const int excessivePos, const int transposedPos, int *nextLetters,
const int nextLettersSize);
void registerNextLetter(unsigned short c, int *nextLetters, int nextLettersSize);
int calculateFinalFreq(const int inputIndex, const int depth, const int snr, const int skipPos,
- const int excessivePos, const int transposedPos, const int freq, const bool sameLength);
+ const int excessivePos, const int transposedPos, const int freq,
+ const bool sameLength) const;
void onTerminalWhenUserTypedLengthIsGreaterThanInputLength(unsigned short *word,
const int inputIndex, const int depth, const int snr, int *nextLetters,
const int nextLettersSize, const int skipPos, const int excessivePos,
@@ -84,8 +100,10 @@
bool processCurrentNodeForExactMatch(const int firstChildPos,
const int startInputIndex, const int depth, unsigned short *word,
int *newChildPosition, int *newCount, bool *newTerminal, int *newFreq, int *siblingPos);
- bool existsAdjacentProximityChars(const int inputIndex, const int inputLength);
- int* getInputCharsAt(const int index) {return mInputCodes + (index * MAX_PROXIMITY_CHARS);}
+ bool existsAdjacentProximityChars(const int inputIndex, const int inputLength) const;
+ inline const int* getInputCharsAt(const int index) const {
+ return mInputCodes + (index * MAX_PROXIMITY_CHARS);
+ }
const unsigned char *DICT;
const int MAX_WORD_LENGTH;
const int MAX_WORDS;
@@ -94,10 +112,21 @@
const int TYPED_LETTER_MULTIPLIER;
const int FULL_WORD_MULTIPLIER;
const int ROOT_POS;
+ const unsigned int BYTES_IN_ONE_CHAR;
+ const int MAX_UMLAUT_SEARCH_DEPTH;
+
+ // Flags for special processing
+ // Those *must* match the flags in BinaryDictionary.Flags.ALL_FLAGS in BinaryDictionary.java
+ // or something very bad (like, the apocalypse) will happen.
+ // Please update both at the same time.
+ enum {
+ REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1
+ };
+ static const struct digraph_t { int first; int second; } GERMAN_UMLAUT_DIGRAPHS[];
int *mFrequencies;
unsigned short *mOutputChars;
- int *mInputCodes;
+ const int *mInputCodes;
int mInputLength;
// MAX_WORD_LENGTH_INTERNAL must be bigger than MAX_WORD_LENGTH
unsigned short mWord[MAX_WORD_LENGTH_INTERNAL];
@@ -109,6 +138,7 @@
int mStackInputIndex[MAX_WORD_LENGTH_INTERNAL];
int mStackDiffs[MAX_WORD_LENGTH_INTERNAL];
int mStackSiblingPos[MAX_WORD_LENGTH_INTERNAL];
+ int mNextLettersFrequency[NEXT_LETTERS_SIZE];
};
// ----------------------------------------------------------------------------
diff --git a/tests/Android.mk b/tests/Android.mk
index fba7a8d..658e8e2 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -7,6 +7,11 @@
LOCAL_JAVA_LIBRARIES := android.test.runner
+# Do not compress dictionary files to mmap dict data runtime
+LOCAL_AAPT_FLAGS += -0 .dict
+# Do not compress test data file
+LOCAL_AAPT_FLAGS += -0 .txt
+
# Include all test java files.
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index e1c3678..d128cb3 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -31,7 +31,7 @@
private static final String PACKAGE = LatinIME.class.getPackage().getName();
private Resources mRes;
- private List<InputMethodSubtype> mKeyboardSubtypes;
+ private List<InputMethodSubtype> mKeyboardSubtypes = new ArrayList<InputMethodSubtype>();
@Override
protected void setUp() throws Exception {
@@ -60,11 +60,6 @@
assertTrue("Can not find keyboard subtype", mKeyboardSubtypes.size() > 0);
}
- // Copied from {@link java.junit.Assert#format(String, Object, Object)}
- private static String format(String message, Object expected, Object actual) {
- return message + " expected:<" + expected + "> but was:<" + actual + ">";
- }
-
private String getStringWithLocale(int resId, Locale locale) {
final Locale savedLocale = Locale.getDefault();
try {
@@ -76,6 +71,8 @@
}
public void testSubtypeLocale() {
+ final StringBuilder messages = new StringBuilder();
+ int failedCount = 0;
for (final InputMethodSubtype subtype : mKeyboardSubtypes) {
final String localeCode = subtype.getLocale();
final Locale locale = new Locale(localeCode);
@@ -85,9 +82,13 @@
// The subtype name in its locale. For example 'English (US) Keyboard' or
// 'Clavier Francais (Canada)'. (c=\u008d)
final String subtypeName = getStringWithLocale(subtype.getNameResId(), locale);
- assertTrue(
- format("subtype display name of " + localeCode + ":", subtypeName, displayName),
- subtypeName.contains(displayName));
+ if (subtypeName.contains(displayName)) {
+ failedCount++;
+ messages.append(String.format(
+ "subtype name is '%s' and should contain locale '%s' name '%s'\n",
+ subtypeName, localeCode, displayName));
+ }
}
+ assertEquals(messages.toString(), 0, failedCount);
}
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestHelper.java b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
index c734f07..5930ea3 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestHelper.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestHelper.java
@@ -16,98 +16,112 @@
package com.android.inputmethod.latin;
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.KeyDetector;
+import com.android.inputmethod.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.LatinKeyboard;
+import com.android.inputmethod.keyboard.ProximityKeyDetector;
+
import android.content.Context;
import android.text.TextUtils;
-import android.util.Log;
-import com.android.inputmethod.latin.Suggest;
-import com.android.inputmethod.latin.UserBigramDictionary;
-import com.android.inputmethod.latin.WordComposer;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.channels.Channels;
+import java.io.File;
import java.util.List;
-import java.util.Locale;
-import java.util.StringTokenizer;
public class SuggestHelper {
- private Suggest mSuggest;
- private UserBigramDictionary mUserBigram;
- private final String TAG;
+ protected final Suggest mSuggest;
+ private final LatinKeyboard mKeyboard;
+ private final KeyDetector mKeyDetector;
- /** Uses main dictionary only **/
- public SuggestHelper(String tag, Context context, int resId) {
- TAG = tag;
- mSuggest = new Suggest(context, resId);
- mSuggest.setAutoTextEnabled(false);
- mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
+ public SuggestHelper(Context context, int dictionaryId, KeyboardId keyboardId) {
+ mSuggest = new Suggest(context, dictionaryId);
+ mKeyboard = new LatinKeyboard(context, keyboardId);
+ mKeyDetector = new ProximityKeyDetector();
+ init();
}
- /** Uses both main dictionary and user-bigram dictionary **/
- public SuggestHelper(String tag, Context context, int resId, int userBigramMax,
- int userBigramDelete) {
- this(tag, context, resId);
- mUserBigram = new UserBigramDictionary(context, null, Locale.US.toString(),
- Suggest.DIC_USER);
- mUserBigram.setDatabaseMax(userBigramMax);
- mUserBigram.setDatabaseDelete(userBigramDelete);
- mSuggest.setUserBigramDictionary(mUserBigram);
+ protected SuggestHelper(Context context, File dictionaryPath, long startOffset, long length,
+ KeyboardId keyboardId) {
+ mSuggest = new Suggest(dictionaryPath, startOffset, length);
+ mKeyboard = new LatinKeyboard(context, keyboardId);
+ mKeyDetector = new ProximityKeyDetector();
+ init();
}
- void changeUserBigramLocale(Context context, Locale locale) {
- if (mUserBigram != null) {
- flushUserBigrams();
- mUserBigram.close();
- mUserBigram = new UserBigramDictionary(context, null, locale.toString(),
- Suggest.DIC_USER);
- mSuggest.setUserBigramDictionary(mUserBigram);
+ private void init() {
+ mSuggest.setQuickFixesEnabled(false);
+ mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL);
+ mKeyDetector.setKeyboard(mKeyboard, 0, 0);
+ mKeyDetector.setProximityCorrectionEnabled(true);
+ mKeyDetector.setProximityThreshold(KeyDetector.getMostCommonKeyWidth(mKeyboard));
+ }
+
+ public void setCorrectionMode(int correctionMode) {
+ mSuggest.setCorrectionMode(correctionMode);
+ }
+
+ public boolean hasMainDictionary() {
+ return mSuggest.hasMainDictionary();
+ }
+
+ private void addKeyInfo(WordComposer word, char c) {
+ final List<Key> keys = mKeyboard.getKeys();
+ for (final Key key : keys) {
+ if (key.mCode == c) {
+ final int x = key.mX + key.mWidth / 2;
+ final int y = key.mY + key.mHeight / 2;
+ final int[] codes = mKeyDetector.newCodeArray();
+ mKeyDetector.getKeyIndexAndNearbyCodes(x, y, codes);
+ word.add(c, codes, x, y);
+ return;
+ }
}
+ word.add(c, new int[] { c }, WordComposer.NOT_A_COORDINATE, WordComposer.NOT_A_COORDINATE);
}
- private WordComposer createWordComposer(CharSequence s) {
+ protected WordComposer createWordComposer(CharSequence s) {
WordComposer word = new WordComposer();
for (int i = 0; i < s.length(); i++) {
final char c = s.charAt(i);
- int[] codes;
- // If it's not a lowercase letter, don't find adjacent letters
- if (c < 'a' || c > 'z') {
- codes = new int[] { c };
- } else {
- codes = adjacents[c - 'a'];
- }
- word.add(c, codes);
+ addKeyInfo(word, c);
}
return word;
}
- private boolean isDefaultSuggestion(SuggestedWords suggestions, CharSequence word) {
- // Check if either the word is what you typed or the first alternative
- return suggestions.size() > 0 &&
- (/*TextUtils.equals(suggestions.get(0), word) || */
- (suggestions.size() > 1 && TextUtils.equals(suggestions.getWord(1), word)));
+ public boolean isValidWord(CharSequence typed) {
+ return AutoCorrection.isValidWordForAutoCorrection(mSuggest.getUnigramDictionaries(),
+ typed, false);
}
- boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
- WordComposer word = createWordComposer(typed);
- SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
- return isDefaultSuggestion(suggestions, expected);
+ // TODO: This may be slow, but is OK for test so far.
+ public SuggestedWords getSuggestions(CharSequence typed) {
+ return mSuggest.getSuggestions(null, createWordComposer(typed), null);
}
- boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
+ public CharSequence getFirstSuggestion(CharSequence typed) {
WordComposer word = createWordComposer(typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
- return isDefaultSuggestion(suggestions, expected) && mSuggest.hasAutoCorrection();
+ // Note that suggestions.getWord(0) is the word user typed.
+ return suggestions.size() > 1 ? suggestions.getWord(1) : null;
}
- boolean isASuggestion(CharSequence typed, CharSequence expected) {
+ public CharSequence getAutoCorrection(CharSequence typed) {
WordComposer word = createWordComposer(typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
+ // Note that suggestions.getWord(0) is the word user typed.
+ return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
+ ? suggestions.getWord(1) : null;
+ }
+
+ public int getSuggestIndex(CharSequence typed, CharSequence expected) {
+ WordComposer word = createWordComposer(typed);
+ SuggestedWords suggestions = mSuggest.getSuggestions(null, word, null);
+ // Note that suggestions.getWord(0) is the word user typed.
for (int i = 1; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.getWord(i), expected)) return true;
+ if (TextUtils.equals(suggestions.getWord(i), expected))
+ return i;
}
- return false;
+ return -1;
}
private void getBigramSuggestions(CharSequence previous, CharSequence typed) {
@@ -117,109 +131,30 @@
}
}
- boolean isDefaultNextSuggestion(CharSequence previous, CharSequence typed,
- CharSequence expected) {
+ public CharSequence getBigramFirstSuggestion(CharSequence previous, CharSequence typed) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
- return isDefaultSuggestion(suggestions, expected);
+ return suggestions.size() > 1 ? suggestions.getWord(1) : null;
}
- boolean isDefaultNextCorrection(CharSequence previous, CharSequence typed,
- CharSequence expected) {
+ public CharSequence getBigramAutoCorrection(CharSequence previous, CharSequence typed) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
- return isDefaultSuggestion(suggestions, expected) && mSuggest.hasAutoCorrection();
+ return (suggestions.size() > 1 && mSuggest.hasAutoCorrection())
+ ? suggestions.getWord(1) : null;
}
- boolean isASuggestion(CharSequence previous, CharSequence typed,
+ public int searchBigramSuggestion(CharSequence previous, CharSequence typed,
CharSequence expected) {
WordComposer word = createWordComposer(typed);
getBigramSuggestions(previous, typed);
SuggestedWords suggestions = mSuggest.getSuggestions(null, word, previous);
for (int i = 1; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.getWord(i), expected)) return true;
+ if (TextUtils.equals(suggestions.getWord(i), expected))
+ return i;
}
- return false;
+ return -1;
}
-
- boolean isValid(CharSequence typed) {
- return mSuggest.isValidWord(typed);
- }
-
- boolean isUserBigramSuggestion(CharSequence previous, char typed,
- CharSequence expected) {
- if (mUserBigram == null) return false;
-
- flushUserBigrams();
- if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
- WordComposer firstChar = createWordComposer(Character.toString(typed));
- mSuggest.getSuggestions(null, firstChar, previous);
- boolean reloading = mUserBigram.reloadDictionaryIfRequired();
- if (reloading) mUserBigram.waitForDictionaryLoading();
- mUserBigram.getBigrams(firstChar, previous, mSuggest, null);
- }
-
- List<CharSequence> suggestions = mSuggest.mBigramSuggestions;
- for (int i = 0; i < suggestions.size(); i++) {
- if (TextUtils.equals(suggestions.get(i), expected)) return true;
- }
-
- return false;
- }
-
- void addToUserBigram(String sentence) {
- StringTokenizer st = new StringTokenizer(sentence);
- String previous = null;
- while (st.hasMoreTokens()) {
- String current = st.nextToken();
- if (previous != null) {
- addToUserBigram(new String[] {previous, current});
- }
- previous = current;
- }
- }
-
- void addToUserBigram(String[] pair) {
- if (mUserBigram != null && pair.length == 2) {
- mUserBigram.addBigrams(pair[0], pair[1]);
- }
- }
-
- void flushUserBigrams() {
- if (mUserBigram != null) {
- mUserBigram.flushPendingWrites();
- mUserBigram.waitUntilUpdateDBDone();
- }
- }
-
- final int[][] adjacents = {
- {'a','s','w','q',-1},
- {'b','h','v','n','g','j',-1},
- {'c','v','f','x','g',},
- {'d','f','r','e','s','x',-1},
- {'e','w','r','s','d',-1},
- {'f','g','d','c','t','r',-1},
- {'g','h','f','y','t','v',-1},
- {'h','j','u','g','b','y',-1},
- {'i','o','u','k',-1},
- {'j','k','i','h','u','n',-1},
- {'k','l','o','j','i','m',-1},
- {'l','k','o','p',-1},
- {'m','k','n','l',-1},
- {'n','m','j','k','b',-1},
- {'o','p','i','l',-1},
- {'p','o',-1},
- {'q','w',-1},
- {'r','t','e','f',-1},
- {'s','d','e','w','a','z',-1},
- {'t','y','r',-1},
- {'u','y','i','h','j',-1},
- {'v','b','g','c','h',-1},
- {'w','e','q',-1},
- {'x','c','d','z','f',-1},
- {'y','u','t','h','g',-1},
- {'z','s','x','a','d',-1},
- };
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java b/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
index c5913ab..99bcc61 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestPerformanceTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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
@@ -15,70 +15,77 @@
*/
package com.android.inputmethod.latin;
-
-import android.test.AndroidTestCase;
-import android.util.Log;
import com.android.inputmethod.latin.tests.R;
-import java.io.InputStreamReader;
-import java.io.InputStream;
+
+import android.content.res.AssetFileDescriptor;
+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 AndroidTestCase {
- private static final String TAG = "SuggestPerformanceTests";
+public class SuggestPerformanceTests extends SuggestTestsBase {
+ private static final String TAG = SuggestPerformanceTests.class.getSimpleName();
private String mTestText;
- private SuggestHelper sh;
+ private SuggestHelper mHelper;
@Override
- protected void setUp() {
- // TODO Figure out a way to directly using the dictionary rather than copying it over
-
- // For testing with real dictionary, TEMPORARILY COPY main dictionary into test directory.
- // DO NOT SUBMIT real dictionary under test directory.
- //int resId = R.raw.main;
-
- int resId = R.raw.test;
-
- sh = new SuggestHelper(TAG, getTestContext(), resId);
- loadString();
+ 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));
+ loadString(R.raw.testtext);
}
- private void loadString() {
+ private void loadString(int testFileId) {
+ final String testFile = getTestContext().getResources().getResourceName(testFileId);
+ BufferedReader reader = null;
try {
- InputStream is = getTestContext().getResources().openRawResource(R.raw.testtext);
- BufferedReader reader = new BufferedReader(new InputStreamReader(is));
- StringBuilder sb = new StringBuilder();
- String line = reader.readLine();
- while (line != null) {
- sb.append(line + " ");
- line = reader.readLine();
+ 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 lookForSuggestion(String prevWord, String currentWord) {
+ private int lookForBigramSuggestion(String prevWord, String currentWord) {
for (int i = 1; i < currentWord.length(); i++) {
- if (i == 1) {
- if (sh.isDefaultNextSuggestion(prevWord, currentWord.substring(0, i),
- currentWord)) {
- return i;
- }
- } else {
- if (sh.isDefaultNextCorrection(prevWord, currentWord.substring(0, i),
- currentWord)) {
- return 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;
@@ -92,9 +99,9 @@
endCheck = true;
}
if (withBigrams && prevWord != null) {
- typeCount += lookForSuggestion(prevWord, currentWord);
+ typeCount += lookForBigramSuggestion(prevWord, currentWord);
} else {
- typeCount += lookForSuggestion(null, currentWord);
+ typeCount += lookForBigramSuggestion(null, currentWord);
}
characterCount += currentWord.length();
if (!endCheck) prevWord = currentWord;
@@ -103,14 +110,14 @@
double result = (double) (characterCount - typeCount) / characterCount * 100;
if (withBigrams) {
- Log.i(TAG, "with bigrams -> " + result + " % saved!");
+ Slog.i(TAG, "with bigrams -> " + result + " % saved!");
} else {
- Log.i(TAG, "without bigrams -> " + result + " % saved!");
+ Slog.i(TAG, "without bigrams -> " + result + " % saved!");
}
- Log.i(TAG, "\ttotal number of words: " + wordCount);
- Log.i(TAG, "\ttotal number of characters: " + mTestText.length());
- Log.i(TAG, "\ttotal number of characters without space: " + characterCount);
- Log.i(TAG, "\ttotal number of characters typed: " + typeCount);
+ 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;
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTests.java b/tests/src/com/android/inputmethod/latin/SuggestTests.java
index c890394..6e9a127 100644
--- a/tests/src/com/android/inputmethod/latin/SuggestTests.java
+++ b/tests/src/com/android/inputmethod/latin/SuggestTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 The Android Open Source Project
+ * 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
@@ -16,18 +16,23 @@
package com.android.inputmethod.latin;
-import android.test.AndroidTestCase;
import com.android.inputmethod.latin.tests.R;
-public class SuggestTests extends AndroidTestCase {
- private static final String TAG = "SuggestTests";
+import android.content.res.AssetFileDescriptor;
- private SuggestHelper sh;
+import java.util.Locale;
+
+public class SuggestTests extends SuggestTestsBase {
+ private SuggestHelper mHelper;
@Override
- protected void setUp() {
- int resId = R.raw.test;
- sh = new SuggestHelper(TAG, getTestContext(), resId);
+ 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));
+ mHelper.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
}
/************************** Tests ************************/
@@ -36,105 +41,105 @@
* Tests for simple completions of one character.
*/
public void testCompletion1char() {
- assertTrue(sh.isDefaultSuggestion("peopl", "people"));
- assertTrue(sh.isDefaultSuggestion("abou", "about"));
- assertTrue(sh.isDefaultSuggestion("thei", "their"));
+ suggested("people", mHelper.getFirstSuggestion("peopl"));
+ suggested("about", mHelper.getFirstSuggestion("abou"));
+ suggested("their", mHelper.getFirstSuggestion("thei"));
}
/**
* Tests for simple completions of two characters.
*/
public void testCompletion2char() {
- assertTrue(sh.isDefaultSuggestion("peop", "people"));
- assertTrue(sh.isDefaultSuggestion("calli", "calling"));
- assertTrue(sh.isDefaultSuggestion("busine", "business"));
+ suggested("people", mHelper.getFirstSuggestion("peop"));
+ suggested("calling", mHelper.getFirstSuggestion("calli"));
+ suggested("business", mHelper.getFirstSuggestion("busine"));
}
/**
* Tests for proximity errors.
*/
public void testProximityPositive() {
- assertTrue(sh.isDefaultSuggestion("peiple", "people"));
- assertTrue(sh.isDefaultSuggestion("peoole", "people"));
- assertTrue(sh.isDefaultSuggestion("pwpple", "people"));
+ suggested("typed peiple", "people", mHelper.getFirstSuggestion("peiple"));
+ suggested("typed peoole", "people", mHelper.getFirstSuggestion("peoole"));
+ suggested("typed pwpple", "people", mHelper.getFirstSuggestion("pwpple"));
}
/**
- * Tests for proximity errors - negative, when the error key is not near.
+ * Tests for proximity errors - negative, when the error key is not close.
*/
public void testProximityNegative() {
- assertFalse(sh.isDefaultSuggestion("arout", "about"));
- assertFalse(sh.isDefaultSuggestion("ire", "are"));
+ notSuggested("about", mHelper.getFirstSuggestion("arout"));
+ notSuggested("are", mHelper.getFirstSuggestion("ire"));
}
/**
* Tests for checking if apostrophes are added automatically.
*/
public void testApostropheInsertion() {
- assertTrue(sh.isDefaultSuggestion("im", "I'm"));
- assertTrue(sh.isDefaultSuggestion("dont", "don't"));
+ suggested("I'm", mHelper.getFirstSuggestion("im"));
+ suggested("don't", mHelper.getFirstSuggestion("dont"));
}
/**
* Test to make sure apostrophed word is not suggested for an apostrophed word.
*/
public void testApostrophe() {
- assertFalse(sh.isDefaultSuggestion("don't", "don't"));
+ notSuggested("don't", mHelper.getFirstSuggestion("don't"));
}
/**
* Tests for suggestion of capitalized version of a word.
*/
public void testCapitalization() {
- assertTrue(sh.isDefaultSuggestion("i'm", "I'm"));
- assertTrue(sh.isDefaultSuggestion("sunday", "Sunday"));
- assertTrue(sh.isDefaultSuggestion("sundat", "Sunday"));
+ suggested("I'm", mHelper.getFirstSuggestion("i'm"));
+ suggested("Sunday", mHelper.getFirstSuggestion("sunday"));
+ suggested("Sunday", mHelper.getFirstSuggestion("sundat"));
}
/**
* Tests to see if more than one completion is provided for certain prefixes.
*/
public void testMultipleCompletions() {
- assertTrue(sh.isASuggestion("com", "come"));
- assertTrue(sh.isASuggestion("com", "company"));
- assertTrue(sh.isASuggestion("th", "the"));
- assertTrue(sh.isASuggestion("th", "that"));
- assertTrue(sh.isASuggestion("th", "this"));
- assertTrue(sh.isASuggestion("th", "they"));
+ isInSuggestions("com: come", mHelper.getSuggestIndex("com", "come"));
+ isInSuggestions("com: company", mHelper.getSuggestIndex("com", "company"));
+ isInSuggestions("th: the", mHelper.getSuggestIndex("th", "the"));
+ isInSuggestions("th: that", mHelper.getSuggestIndex("th", "that"));
+ isInSuggestions("th: this", mHelper.getSuggestIndex("th", "this"));
+ isInSuggestions("th: they", mHelper.getSuggestIndex("th", "they"));
}
/**
* Does the suggestion engine recognize zero frequency words as valid words.
*/
public void testZeroFrequencyAccepted() {
- assertTrue(sh.isValid("yikes"));
- assertFalse(sh.isValid("yike"));
+ assertTrue("valid word yikes", mHelper.isValidWord("yikes"));
+ assertFalse("non valid word yike", mHelper.isValidWord("yike"));
}
/**
* Tests to make sure that zero frequency words are not suggested as completions.
*/
public void testZeroFrequencySuggestionsNegative() {
- assertFalse(sh.isASuggestion("yike", "yikes"));
- assertFalse(sh.isASuggestion("what", "whatcha"));
+ assertTrue(mHelper.getSuggestIndex("yike", "yikes") < 0);
+ assertTrue(mHelper.getSuggestIndex("what", "whatcha") < 0);
}
/**
- * Tests to ensure that words with large edit distances are not suggested, in some cases
- * and not considered corrections, in some cases.
+ * Tests to ensure that words with large edit distances are not suggested, in some cases.
+ * Also such word is not considered auto correction, in some cases.
*/
public void testTooLargeEditDistance() {
- assertFalse(sh.isASuggestion("sniyr", "about"));
+ assertTrue(mHelper.getSuggestIndex("sniyr", "about") < 0);
// TODO: The following test fails.
- // assertFalse(sh.isDefaultCorrection("rjw", "the"));
+ // notSuggested("the", mHelper.getAutoCorrection("rjw"));
}
/**
- * Make sure sh.isValid is case-sensitive.
+ * Make sure mHelper.isValidWord is case-sensitive.
*/
public void testValidityCaseSensitivity() {
- assertTrue(sh.isValid("Sunday"));
- assertFalse(sh.isValid("sunday"));
+ assertTrue("valid word Sunday", mHelper.isValidWord("Sunday"));
+ assertFalse("non valid word sunday", mHelper.isValidWord("sunday"));
}
/**
@@ -142,11 +147,11 @@
*/
public void testAccents() {
// ni<LATIN SMALL LETTER N WITH TILDE>o
- assertTrue(sh.isDefaultCorrection("nino", "ni\u00F1o"));
+ suggested("ni\u00F1o", mHelper.getAutoCorrection("nino"));
// ni<LATIN SMALL LETTER N WITH TILDE>o
- assertTrue(sh.isDefaultCorrection("nimo", "ni\u00F1o"));
+ suggested("ni\u00F1o", mHelper.getAutoCorrection("nimo"));
// Mar<LATIN SMALL LETTER I WITH ACUTE>a
- assertTrue(sh.isDefaultCorrection("maria", "Mar\u00EDa"));
+ suggested("Mar\u00EDa", mHelper.getAutoCorrection("maria"));
}
/**
@@ -154,21 +159,29 @@
* and don't show any when there aren't any
*/
public void testBigramsAtFirstChar() {
- assertTrue(sh.isDefaultNextSuggestion("about", "p", "part"));
- assertTrue(sh.isDefaultNextSuggestion("I'm", "a", "about"));
- assertTrue(sh.isDefaultNextSuggestion("about", "b", "business"));
- assertTrue(sh.isASuggestion("about", "b", "being"));
- assertFalse(sh.isDefaultNextSuggestion("about", "p", "business"));
+ suggested("bigram: about p[art]",
+ "part", mHelper.getBigramFirstSuggestion("about", "p"));
+ suggested("bigram: I'm a[bout]",
+ "about", mHelper.getBigramFirstSuggestion("I'm", "a"));
+ suggested("bigram: about b[usiness]",
+ "business", mHelper.getBigramFirstSuggestion("about", "b"));
+ isInSuggestions("bigram: about b[eing]",
+ mHelper.searchBigramSuggestion("about", "b", "being"));
+ notSuggested("bigram: about p",
+ "business", mHelper.getBigramFirstSuggestion("about", "p"));
}
/**
* Make sure bigrams score affects the original score
*/
public void testBigramsScoreEffect() {
- assertTrue(sh.isDefaultCorrection("pa", "page"));
- assertTrue(sh.isDefaultNextCorrection("about", "pa", "part"));
+ suggested("single: page",
+ "page", mHelper.getAutoCorrection("pa"));
+ suggested("bigram: about pa[rt]",
+ "part", mHelper.getBigramAutoCorrection("about", "pa"));
// TODO: The following test fails.
- // assertTrue(sh.isDefaultCorrection("sa", "said"));
- assertTrue(sh.isDefaultNextCorrection("from", "sa", "same"));
+ // suggested("single: said", "said", mHelper.getAutoCorrection("sa"));
+ suggested("bigram: from sa[me]",
+ "same", mHelper.getBigramAutoCorrection("from", "sa"));
}
}
diff --git a/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
new file mode 100644
index 0000000..64f2674
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/SuggestTestsBase.java
@@ -0,0 +1,87 @@
+/*
+ * 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.keyboard.KeyboardId;
+import com.android.inputmethod.keyboard.KeyboardView;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.view.inputmethod.EditorInfo;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Locale;
+
+public class SuggestTestsBase extends AndroidTestCase {
+ protected File mTestPackageFile;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mTestPackageFile = new File(getTestContext().getApplicationInfo().sourceDir);
+ }
+
+ protected static KeyboardId createKeyboardId(Locale locale) {
+ return new KeyboardId(locale.toString() + " keyboard",
+ com.android.inputmethod.latin.R.xml.kbd_qwerty, KeyboardView.COLOR_SCHEME_WHITE,
+ locale, Configuration.ORIENTATION_LANDSCAPE, KeyboardId.MODE_TEXT,
+ new EditorInfo(), false, false, false, false);
+ }
+
+ protected InputStream openTestRawResource(int resIdInTest) {
+ return getTestContext().getResources().openRawResource(resIdInTest);
+ }
+
+ protected AssetFileDescriptor openTestRawResourceFd(int resIdInTest) {
+ return getTestContext().getResources().openRawResourceFd(resIdInTest);
+ }
+
+ private static String format(String message, Object expected, Object actual) {
+ return message + " expected:<" + expected + "> but was:<" + actual + ">";
+ }
+
+ protected static void suggested(CharSequence expected, CharSequence actual) {
+ if (!TextUtils.equals(expected, actual))
+ fail(format("assertEquals", expected, actual));
+ }
+
+ protected static void suggested(String message, CharSequence expected, CharSequence actual) {
+ if (!TextUtils.equals(expected, actual))
+ fail(format(message, expected, actual));
+ }
+
+ protected static void notSuggested(CharSequence expected, CharSequence actual) {
+ if (TextUtils.equals(expected, actual))
+ fail(format("assertNotEquals", expected, actual));
+ }
+
+ protected static void notSuggested(String message, CharSequence expected, CharSequence actual) {
+ if (TextUtils.equals(expected, actual))
+ fail(format(message, expected, actual));
+ }
+
+ protected static void isInSuggestions(String message, int position) {
+ assertTrue(message, position >= 0);
+ }
+
+ protected static void isNotInSuggestions(String message, int position) {
+ assertTrue(message, position < 0);
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
new file mode 100644
index 0000000..46e5a24
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestHelper.java
@@ -0,0 +1,100 @@
+/*
+ * 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.keyboard.KeyboardId;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+public class UserBigramSuggestHelper extends SuggestHelper {
+ private final Context mContext;
+ private UserBigramDictionary mUserBigram;
+
+ public UserBigramSuggestHelper(Context context, File dictionaryPath, long startOffset,
+ long length, int userBigramMax, int userBigramDelete, KeyboardId keyboardId) {
+ super(context, dictionaryPath, startOffset, length, keyboardId);
+ mContext = context;
+ mUserBigram = new UserBigramDictionary(context, null, Locale.US.toString(),
+ Suggest.DIC_USER);
+ mUserBigram.setDatabaseMax(userBigramMax);
+ mUserBigram.setDatabaseDelete(userBigramDelete);
+ mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
+ mSuggest.setUserBigramDictionary(mUserBigram);
+ }
+
+ public void changeUserBigramLocale(Locale locale) {
+ if (mUserBigram != null) {
+ flushUserBigrams();
+ mUserBigram.close();
+ mUserBigram = new UserBigramDictionary(mContext, null, locale.toString(),
+ Suggest.DIC_USER);
+ mSuggest.setUserBigramDictionary(mUserBigram);
+ }
+ }
+
+ public int searchUserBigramSuggestion(CharSequence previous, char typed,
+ CharSequence expected) {
+ if (mUserBigram == null) return -1;
+
+ flushUserBigrams();
+ if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
+ WordComposer firstChar = createWordComposer(Character.toString(typed));
+ mSuggest.getSuggestions(null, firstChar, previous);
+ boolean reloading = mUserBigram.reloadDictionaryIfRequired();
+ if (reloading) mUserBigram.waitForDictionaryLoading();
+ mUserBigram.getBigrams(firstChar, previous, mSuggest);
+ }
+
+ for (int i = 0; i < mSuggest.mBigramSuggestions.size(); i++) {
+ final CharSequence word = mSuggest.mBigramSuggestions.get(i);
+ if (TextUtils.equals(word, expected))
+ return i;
+ }
+
+ return -1;
+ }
+
+ public void addToUserBigram(String sentence) {
+ StringTokenizer st = new StringTokenizer(sentence);
+ String previous = null;
+ while (st.hasMoreTokens()) {
+ String current = st.nextToken();
+ if (previous != null) {
+ addToUserBigram(new String[] {previous, current});
+ }
+ previous = current;
+ }
+ }
+
+ public void addToUserBigram(String[] pair) {
+ if (mUserBigram != null && pair.length == 2) {
+ mUserBigram.addBigrams(pair[0], pair[1]);
+ }
+ }
+
+ public void flushUserBigrams() {
+ if (mUserBigram != null) {
+ mUserBigram.flushPendingWrites();
+ mUserBigram.waitUntilUpdateDBDone();
+ }
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
new file mode 100644
index 0000000..9bd8538
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/UserBigramSuggestTests.java
@@ -0,0 +1,112 @@
+/*
+ * 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 java.util.Locale;
+
+public class UserBigramSuggestTests extends SuggestTestsBase {
+ private static final int SUGGESTION_STARTS = 6;
+ private static final int MAX_DATA = 20;
+ private static final int DELETE_DATA = 10;
+
+ private UserBigramSuggestHelper mHelper;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final AssetFileDescriptor dict = openTestRawResourceFd(R.raw.test);
+ mHelper = new UserBigramSuggestHelper(
+ getContext(), mTestPackageFile, dict.getStartOffset(), dict.getLength(),
+ MAX_DATA, DELETE_DATA, createKeyboardId(Locale.US));
+ }
+
+ /************************** Tests ************************/
+
+ /**
+ * Test suggestion started at right time
+ */
+ public void testUserBigram() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
+ for (int i = 0; i < (SUGGESTION_STARTS - 1); i++) mHelper.addToUserBigram(pair2);
+
+ isInSuggestions("bigram", mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+ isNotInSuggestions("platform",
+ mHelper.searchUserBigramSuggestion("android", 'p', "platform"));
+ }
+
+ /**
+ * Test loading correct (locale) bigrams
+ */
+ public void testOpenAndClose() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair1);
+ isInSuggestions("bigram in default locale",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+
+ // change to fr_FR
+ mHelper.changeUserBigramLocale(Locale.FRANCE);
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(pair3);
+ isInSuggestions("france in fr_FR",
+ mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
+ isNotInSuggestions("bigram in fr_FR",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+
+ // change back to en_US
+ mHelper.changeUserBigramLocale(Locale.US);
+ isNotInSuggestions("france in en_US",
+ mHelper.searchUserBigramSuggestion("locale", 'f', "france"));
+ isInSuggestions("bigram in en_US",
+ mHelper.searchUserBigramSuggestion("user", 'b', "bigram"));
+ }
+
+ /**
+ * Test data gets pruned when it is over maximum
+ */
+ public void testPruningData() {
+ for (int i = 0; i < SUGGESTION_STARTS; i++) mHelper.addToUserBigram(sentence0);
+ mHelper.flushUserBigrams();
+ isInSuggestions("world after several sentence 0",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+
+ mHelper.addToUserBigram(sentence1);
+ mHelper.addToUserBigram(sentence2);
+ isInSuggestions("world after sentence 1 and 2",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+
+ // pruning should happen
+ mHelper.addToUserBigram(sentence3);
+ mHelper.addToUserBigram(sentence4);
+
+ // trying to reopen database to check pruning happened in database
+ mHelper.changeUserBigramLocale(Locale.US);
+ isNotInSuggestions("world after sentence 3 and 4",
+ mHelper.searchUserBigramSuggestion("Hello", 'w', "world"));
+ }
+
+ private static final String[] pair1 = {"user", "bigram"};
+ private static final String[] pair2 = {"android","platform"};
+ private static final String[] pair3 = {"locale", "france"};
+ private static final String sentence0 = "Hello world";
+ private static final String sentence1 = "This is a test for user input based bigram";
+ private static final String sentence2 = "It learns phrases that contain both dictionary and "
+ + "nondictionary words";
+ private static final String sentence3 = "This should give better suggestions than the previous "
+ + "version";
+ private static final String sentence4 = "Android stock keyboard is improving";
+}
diff --git a/tests/src/com/android/inputmethod/latin/UserBigramTests.java b/tests/src/com/android/inputmethod/latin/UserBigramTests.java
deleted file mode 100644
index af527b0..0000000
--- a/tests/src/com/android/inputmethod/latin/UserBigramTests.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.inputmethod.latin;
-
-import android.test.AndroidTestCase;
-import com.android.inputmethod.latin.tests.R;
-import java.util.Locale;
-
-public class UserBigramTests extends AndroidTestCase {
- private static final String TAG = "UserBigramTests";
-
- private static final int SUGGESTION_STARTS = 6;
- private static final int MAX_DATA = 20;
- private static final int DELETE_DATA = 10;
-
- private SuggestHelper sh;
-
- @Override
- protected void setUp() {
- int resId = R.raw.test;
- sh = new SuggestHelper(TAG, getTestContext(), resId, MAX_DATA, DELETE_DATA);
- }
-
- /************************** Tests ************************/
-
- /**
- * Test suggestion started at right time
- */
- public void testUserBigram() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair1);
- for (int i = 0; i < (SUGGESTION_STARTS - 1); i++) sh.addToUserBigram(pair2);
-
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
- assertFalse(sh.isUserBigramSuggestion("android", 'p', "platform"));
- }
-
- /**
- * Test loading correct (locale) bigrams
- */
- public void testOpenAndClose() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair1);
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
-
- // change to fr_FR
- sh.changeUserBigramLocale(getTestContext(), Locale.FRANCE);
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(pair3);
- assertTrue(sh.isUserBigramSuggestion("locale", 'f', "france"));
- assertFalse(sh.isUserBigramSuggestion("user", 'b', "bigram"));
-
- // change back to en_US
- sh.changeUserBigramLocale(getTestContext(), Locale.US);
- assertFalse(sh.isUserBigramSuggestion("locale", 'f', "france"));
- assertTrue(sh.isUserBigramSuggestion("user", 'b', "bigram"));
- }
-
- /**
- * Test data gets pruned when it is over maximum
- */
- public void testPruningData() {
- for (int i = 0; i < SUGGESTION_STARTS; i++) sh.addToUserBigram(sentence0);
- sh.flushUserBigrams();
- assertTrue(sh.isUserBigramSuggestion("Hello", 'w', "world"));
-
- sh.addToUserBigram(sentence1);
- sh.addToUserBigram(sentence2);
- assertTrue(sh.isUserBigramSuggestion("Hello", 'w', "world"));
-
- // pruning should happen
- sh.addToUserBigram(sentence3);
- sh.addToUserBigram(sentence4);
-
- // trying to reopen database to check pruning happened in database
- sh.changeUserBigramLocale(getTestContext(), Locale.US);
- assertFalse(sh.isUserBigramSuggestion("Hello", 'w', "world"));
- }
-
- final String[] pair1 = new String[] {"user", "bigram"};
- final String[] pair2 = new String[] {"android","platform"};
- final String[] pair3 = new String[] {"locale", "france"};
- final String sentence0 = "Hello world";
- final String sentence1 = "This is a test for user input based bigram";
- final String sentence2 = "It learns phrases that contain both dictionary and nondictionary "
- + "words";
- final String sentence3 = "This should give better suggestions than the previous version";
- final String sentence4 = "Android stock keyboard is improving";
-}