diff options
231 files changed, 6843 insertions, 3328 deletions
diff --git a/dictionaries/cs_wordlist.combined.gz b/dictionaries/cs_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..8cbf2e961 --- /dev/null +++ b/dictionaries/cs_wordlist.combined.gz diff --git a/dictionaries/cs_wordlist.xml.gz b/dictionaries/cs_wordlist.xml.gz Binary files differdeleted file mode 100644 index f99148b07..000000000 --- a/dictionaries/cs_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/da_wordlist.combined.gz b/dictionaries/da_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..1cccb8632 --- /dev/null +++ b/dictionaries/da_wordlist.combined.gz diff --git a/dictionaries/da_wordlist.xml.gz b/dictionaries/da_wordlist.xml.gz Binary files differdeleted file mode 100644 index a3d4318e2..000000000 --- a/dictionaries/da_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/de_wordlist.combined.gz b/dictionaries/de_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..5db1aa4f3 --- /dev/null +++ b/dictionaries/de_wordlist.combined.gz diff --git a/dictionaries/de_wordlist.xml.gz b/dictionaries/de_wordlist.xml.gz Binary files differdeleted file mode 100644 index a4267b35a..000000000 --- a/dictionaries/de_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/el_wordlist.combined.gz b/dictionaries/el_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..b61da8918 --- /dev/null +++ b/dictionaries/el_wordlist.combined.gz diff --git a/dictionaries/el_wordlist.xml.gz b/dictionaries/el_wordlist.xml.gz Binary files differdeleted file mode 100644 index af1d71d47..000000000 --- a/dictionaries/el_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/en_GB_wordlist.combined.gz b/dictionaries/en_GB_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..b5909c2da --- /dev/null +++ b/dictionaries/en_GB_wordlist.combined.gz diff --git a/dictionaries/en_US_wordlist.combined.gz b/dictionaries/en_US_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..03ea2b787 --- /dev/null +++ b/dictionaries/en_US_wordlist.combined.gz diff --git a/dictionaries/en_emoji.combined.gz b/dictionaries/en_emoji.combined.gz Binary files differnew file mode 100644 index 000000000..0fc009df7 --- /dev/null +++ b/dictionaries/en_emoji.combined.gz diff --git a/dictionaries/en_gb_wordlist.xml.gz b/dictionaries/en_gb_wordlist.xml.gz Binary files differdeleted file mode 100644 index 274424cf2..000000000 --- a/dictionaries/en_gb_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/en_us_wordlist.xml.gz b/dictionaries/en_us_wordlist.xml.gz Binary files differdeleted file mode 100644 index b5054ef3c..000000000 --- a/dictionaries/en_us_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/en_whitelist.xml.gz b/dictionaries/en_whitelist.xml.gz Binary files differdeleted file mode 100644 index cf70a1a84..000000000 --- a/dictionaries/en_whitelist.xml.gz +++ /dev/null diff --git a/dictionaries/en_wordlist.combined.gz b/dictionaries/en_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..62c454049 --- /dev/null +++ b/dictionaries/en_wordlist.combined.gz diff --git a/dictionaries/en_wordlist.xml.gz b/dictionaries/en_wordlist.xml.gz Binary files differdeleted file mode 100644 index 6e57f4212..000000000 --- a/dictionaries/en_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/es_wordlist.combined.gz b/dictionaries/es_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..b0a137c4a --- /dev/null +++ b/dictionaries/es_wordlist.combined.gz diff --git a/dictionaries/es_wordlist.xml.gz b/dictionaries/es_wordlist.xml.gz Binary files differdeleted file mode 100644 index aaf0aa348..000000000 --- a/dictionaries/es_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/fi_wordlist.combined.gz b/dictionaries/fi_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..32dc126c0 --- /dev/null +++ b/dictionaries/fi_wordlist.combined.gz diff --git a/dictionaries/fi_wordlist.xml.gz b/dictionaries/fi_wordlist.xml.gz Binary files differdeleted file mode 100644 index b03565c4f..000000000 --- a/dictionaries/fi_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/fr_emoji.combined.gz b/dictionaries/fr_emoji.combined.gz Binary files differnew file mode 100644 index 000000000..f2929770d --- /dev/null +++ b/dictionaries/fr_emoji.combined.gz diff --git a/dictionaries/fr_wordlist.combined.gz b/dictionaries/fr_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..95a87e630 --- /dev/null +++ b/dictionaries/fr_wordlist.combined.gz diff --git a/dictionaries/fr_wordlist.xml.gz b/dictionaries/fr_wordlist.xml.gz Binary files differdeleted file mode 100644 index 3134a040a..000000000 --- a/dictionaries/fr_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/hr_wordlist.combined.gz b/dictionaries/hr_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..573c3e8ac --- /dev/null +++ b/dictionaries/hr_wordlist.combined.gz diff --git a/dictionaries/hr_wordlist.xml.gz b/dictionaries/hr_wordlist.xml.gz Binary files differdeleted file mode 100644 index 13998d9d5..000000000 --- a/dictionaries/hr_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/it_wordlist.combined.gz b/dictionaries/it_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..d143bc425 --- /dev/null +++ b/dictionaries/it_wordlist.combined.gz diff --git a/dictionaries/it_wordlist.xml.gz b/dictionaries/it_wordlist.xml.gz Binary files differdeleted file mode 100644 index a75553d45..000000000 --- a/dictionaries/it_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/lt_wordlist.combined.gz b/dictionaries/lt_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..03cfa8426 --- /dev/null +++ b/dictionaries/lt_wordlist.combined.gz diff --git a/dictionaries/lt_wordlist.xml.gz b/dictionaries/lt_wordlist.xml.gz Binary files differdeleted file mode 100644 index 8f00f6393..000000000 --- a/dictionaries/lt_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/lv_wordlist.combined.gz b/dictionaries/lv_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..6b2ee77d6 --- /dev/null +++ b/dictionaries/lv_wordlist.combined.gz diff --git a/dictionaries/lv_wordlist.xml.gz b/dictionaries/lv_wordlist.xml.gz Binary files differdeleted file mode 100644 index 453ebb518..000000000 --- a/dictionaries/lv_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/nb_wordlist.combined.gz b/dictionaries/nb_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..0644fc93a --- /dev/null +++ b/dictionaries/nb_wordlist.combined.gz diff --git a/dictionaries/nb_wordlist.xml.gz b/dictionaries/nb_wordlist.xml.gz Binary files differdeleted file mode 100644 index 91813b66c..000000000 --- a/dictionaries/nb_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/nl_wordlist.combined.gz b/dictionaries/nl_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..748c5ed77 --- /dev/null +++ b/dictionaries/nl_wordlist.combined.gz diff --git a/dictionaries/nl_wordlist.xml.gz b/dictionaries/nl_wordlist.xml.gz Binary files differdeleted file mode 100644 index 72ebc6136..000000000 --- a/dictionaries/nl_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/pl_wordlist.combined.gz b/dictionaries/pl_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..638c8eef5 --- /dev/null +++ b/dictionaries/pl_wordlist.combined.gz diff --git a/dictionaries/pl_wordlist.xml.gz b/dictionaries/pl_wordlist.xml.gz Binary files differdeleted file mode 100644 index 5909a5f7f..000000000 --- a/dictionaries/pl_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/pt_BR_wordlist.combined.gz b/dictionaries/pt_BR_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..6f7952004 --- /dev/null +++ b/dictionaries/pt_BR_wordlist.combined.gz diff --git a/dictionaries/pt_PT_wordlist.combined.gz b/dictionaries/pt_PT_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..66ed025fb --- /dev/null +++ b/dictionaries/pt_PT_wordlist.combined.gz diff --git a/dictionaries/pt_br_wordlist.xml.gz b/dictionaries/pt_br_wordlist.xml.gz Binary files differdeleted file mode 100644 index 53d4e4298..000000000 --- a/dictionaries/pt_br_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/pt_pt_wordlist.xml.gz b/dictionaries/pt_pt_wordlist.xml.gz Binary files differdeleted file mode 100644 index e3e8c3ac3..000000000 --- a/dictionaries/pt_pt_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/ru_wordlist.combined.gz b/dictionaries/ru_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..62ae12e90 --- /dev/null +++ b/dictionaries/ru_wordlist.combined.gz diff --git a/dictionaries/ru_wordlist.xml.gz b/dictionaries/ru_wordlist.xml.gz Binary files differdeleted file mode 100644 index 877f0608e..000000000 --- a/dictionaries/ru_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/sample.combined b/dictionaries/sample.combined new file mode 100644 index 000000000..4fa595e1e --- /dev/null +++ b/dictionaries/sample.combined @@ -0,0 +1,38 @@ +# This is a sample wordlist that can be converted to a binary dictionary +# for use by the Latin IME. +# The file is essentially a CSV file, with indent level denoting nesting. +# +# The file starts with a single CSV line with the header attributes. Whatever +# the content, these are included as is in the binary file. The first attribute +# of the file should be `dictionary'. Usual fields are `locale', `description', +# `date', `version', `options'. +# +# Each word has a `word' entry and at least a `f' argument denoting its +# probability, as an integer between 0 and 255 on a logarithmic scale, with +# 255 meaning 1 and each decrement in 1 dividing probability by 1.15. +# As a special case, a weight of 0 is taken to mean profanity - words that +# should not be considered a typo, but that should never be suggested +# explicitly. An entry may be made not a word by adding a `not_a_word' +# field with a value of `true'. The main reason for putting such entries +# into the dictionary is to add shortcut targets and maybe a whitelist +# replacement. +# +# Each word may or may not have any number of shortcut target lines +# starting with a `shortcut' entry and having at least a `f' frequency +# value between 0 and 14, or the special value `whitelist' which becomes +# 15, which is then taken to be the whitelist target of this word. +# +# Each word may also have any number of bigram lines starting with a +# `bigram' entry containing the following word whose frequency should +# override the unigram frequency when following the word this bigram is +# for. +# +dictionary=main:en,locale=en,description=Sample wordlist,date=1351495318,version=1 + word=sample,f=200 + bigram=wordlist,f=243 + word=wordlist,f=180 + word=shortcut,f=176 + shortcut=target,f=10 + word=witelisted,f=10,not_a_word=true + shortcut=whitelisted,f=whitelist + word=profanity,f=0 diff --git a/dictionaries/sample.xml b/dictionaries/sample.xml deleted file mode 100644 index ad98f2b6f..000000000 --- a/dictionaries/sample.xml +++ /dev/null @@ -1,17 +0,0 @@ -<!-- This is a sample wordlist that can be converted to a binary dictionary - for use by the Latin IME. - The format of the word list is a flat list of word entries. - Each entry has a frequency between 255 and 0. - Highest frequency words get more weight in the prediction algorithm. As a - special case, a weight of 0 is taken to mean profanity - words that should - not be considered a typo, but that should never be suggested explicitly. - You can capitalize words that must always be capitalized, such as "January". - You can have a capitalized and a non-capitalized word as separate entries, - such as "robin" and "Robin". ---> -<wordlist> - <w f="255">this</w> - <w f="255">is</w> - <w f="128">sample</w> - <w f="1">wordlist</w> -</wordlist> diff --git a/dictionaries/sl_wordlist.combined.gz b/dictionaries/sl_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..845b55a8b --- /dev/null +++ b/dictionaries/sl_wordlist.combined.gz diff --git a/dictionaries/sl_wordlist.xml.gz b/dictionaries/sl_wordlist.xml.gz Binary files differdeleted file mode 100644 index 3927b698e..000000000 --- a/dictionaries/sl_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/sr_wordlist.combined.gz b/dictionaries/sr_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..c15bc48fd --- /dev/null +++ b/dictionaries/sr_wordlist.combined.gz diff --git a/dictionaries/sr_wordlist.xml.gz b/dictionaries/sr_wordlist.xml.gz Binary files differdeleted file mode 100644 index c2eea681f..000000000 --- a/dictionaries/sr_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/sv_wordlist.combined.gz b/dictionaries/sv_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..ec399fbf5 --- /dev/null +++ b/dictionaries/sv_wordlist.combined.gz diff --git a/dictionaries/sv_wordlist.xml.gz b/dictionaries/sv_wordlist.xml.gz Binary files differdeleted file mode 100644 index caa63c435..000000000 --- a/dictionaries/sv_wordlist.xml.gz +++ /dev/null diff --git a/dictionaries/tr_wordlist.combined.gz b/dictionaries/tr_wordlist.combined.gz Binary files differnew file mode 100644 index 000000000..3e6ca3263 --- /dev/null +++ b/dictionaries/tr_wordlist.combined.gz diff --git a/dictionaries/tr_wordlist.xml.gz b/dictionaries/tr_wordlist.xml.gz Binary files differdeleted file mode 100644 index 35999208e..000000000 --- a/dictionaries/tr_wordlist.xml.gz +++ /dev/null diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index 8ffa29d71..5f64768d4 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -27,7 +27,6 @@ <application android:label="@string/aosp_android_keyboard_ime_name" android:icon="@mipmap/ic_ime_settings" - android:backupAgent="BackupAgent" android:killAfterRestore="false" android:supportsRtl="true"> diff --git a/java/proguard.flags b/java/proguard.flags index ac5b7df16..d65924f7c 100644 --- a/java/proguard.flags +++ b/java/proguard.flags @@ -1,64 +1,11 @@ --keep class com.android.inputmethod.latin.BinaryDictionary { - int mDictLength; - <init>(...); +# Keep classes and methods that have the @UsedForTesting annotation +-keep @com.android.inputmethod.annotations.UsedForTesting class * +-keepclassmembers class * { +@com.android.inputmethod.annotations.UsedForTesting *; } --keep class com.android.inputmethod.keyboard.ProximityInfo { - <init>(com.android.inputmethod.keyboard.ProximityInfo); +# Keep classes and methods that have the @ExternallyReferenced annotation +-keep @com.android.inputmethod.annotations.ExternallyReferenced class * +-keepclassmembers class * { +@com.android.inputmethod.annotations.ExternallyReferenced *; } - --keep class com.android.inputmethod.latin.Suggest { - <init>(...); - com.android.inputmethod.latin.SuggestedWords getSuggestions(...); -} - --keep class com.android.inputmethod.latin.AutoCorrection { - java.lang.CharSequence getAutoCorrectionWord(); -} - --keep class com.android.inputmethod.latin.Utils { - boolean equalsIgnoreCase(...); -} - --keep class com.android.inputmethod.latin.InputPointers { - *; -} - --keep class com.android.inputmethod.latin.ResizableIntArray { - *; -} - --keep class com.android.inputmethod.latin.spellcheck.SpellCheckerSettingsFragment { - *; -} - --keep class com.android.inputmethod.keyboard.MainKeyboardView { - # Keep getter/setter methods for ObjectAnimator - int getLanguageOnSpacebarAnimAlpha(); - void setLanguageOnSpacebarAnimAlpha(int); - int getAltCodeKeyWhileTypingAnimAlpha(); - void setAltCodeKeyWhileTypingAnimAlpha(int); -} - --keep class com.android.inputmethod.keyboard.MoreKeysKeyboard$Builder$MoreKeysKeyboardParams { - <init>(...); -} - --keepclasseswithmembernames class * { - native <methods>; -} - --keep class com.android.inputmethod.research.ResearchLogger { - void flush(); - void publishCurrentLogUnit(...); -} - --keep class com.android.inputmethod.keyboard.KeyboardLayoutSet$Builder { - void setTouchPositionCorrectionEnabled(...); -} - -# The support library contains references to newer platform versions. -# Don't warn about those in case this app is linking against an older -# platform version. We know about them, and they are safe. --dontwarn android.support.v4.** --dontwarn android.support.v13.** diff --git a/java/res/layout/suggestion_word.xml b/java/res/layout/suggestion_word.xml index d64cacf04..fa00e041e 100644 --- a/java/res/layout/suggestion_word.xml +++ b/java/res/layout/suggestion_word.xml @@ -18,6 +18,8 @@ */ --> +<!-- Provide a haptic feedback by ourselves based on the keyboard settings. + We just need to ignore the system's haptic feedback settings. --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" @@ -29,6 +31,7 @@ android:paddingTop="0dp" android:paddingRight="@dimen/suggestion_padding" android:paddingBottom="0dp" + android:hapticFeedbackEnabled="false" android:focusable="false" android:clickable="false" android:singleLine="true" diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml index 897bfb712..288e37b06 100644 --- a/java/res/values-de/strings.xml +++ b/java/res/values-de/strings.xml @@ -69,7 +69,7 @@ <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: gespeichert"</string> <string name="label_go_key" msgid="1635148082137219148">"Los"</string> <string name="label_next_key" msgid="362972844525672568">"Weiter"</string> - <string name="label_previous_key" msgid="1211868118071386787">"Vorh."</string> + <string name="label_previous_key" msgid="1211868118071386787">"Zurück"</string> <string name="label_done_key" msgid="2441578748772529288">"Fertig"</string> <string name="label_send_key" msgid="2815056534433717444">"Senden"</string> <string name="label_to_alpha_key" msgid="4793983863798817523">"ABC"</string> @@ -121,7 +121,7 @@ <string name="prefs_enable_log" msgid="6620424505072963557">"Nutzer-Feedback aktivieren"</string> <string name="prefs_description_log" msgid="5827825607258246003">"Tragen Sie zur Verbesserung dieses Eingabemethodeneditors bei, indem Sie automatisch Nutzungsstatistiken und Absturzberichte an Google senden."</string> <string name="keyboard_layout" msgid="8451164783510487501">"Tastaturdesign"</string> - <string name="subtype_en_GB" msgid="88170601942311355">"Englisch (Großbritannien)"</string> + <string name="subtype_en_GB" msgid="88170601942311355">"Englisch (UK)"</string> <string name="subtype_en_US" msgid="6160452336634534239">"Englisch (USA)"</string> <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"Englisch (GB) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string> <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"Englisch (USA) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string> diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml index 00e574e90..ed998a891 100644 --- a/java/res/values-ru/strings.xml +++ b/java/res/values-ru/strings.xml @@ -37,7 +37,7 @@ <string name="advanced_settings_summary" msgid="4487980456152830271">"Для опытных пользователей"</string> <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Другой способ ввода"</string> <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Клавиша переключения языков также служит для смены способа ввода"</string> - <string name="show_language_switch_key" msgid="5915478828318774384">"Клавиша переключения языков"</string> + <string name="show_language_switch_key" msgid="5915478828318774384">"Клавиша смены языка"</string> <string name="show_language_switch_key_summary" msgid="7343403647474265713">"Показывать, когда включено несколько раскладок"</string> <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"Задержка закрытия"</string> <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"Без задержки"</string> @@ -59,7 +59,7 @@ <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"Умеренное"</string> <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"Активное"</string> <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"Очень активно"</string> - <string name="bigram_prediction" msgid="1084449187723948550">"Подсказка следующего слова"</string> + <string name="bigram_prediction" msgid="1084449187723948550">"Подсказывать слова"</string> <string name="bigram_prediction_summary" msgid="3896362682751109677">"Предлагать подсказки на основе предыдущего слова"</string> <string name="gesture_input" msgid="826951152254563827">"Включить функцию"</string> <string name="gesture_input_summary" msgid="9180350639305731231">"Вводите слова, не отрывая пальца от клавиатуры"</string> @@ -106,7 +106,7 @@ <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"Режим ввода текста"</string> <string name="spoken_description_mode_phone" msgid="6520207943132026264">"Режим набора номера"</string> <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"Режим телефонных символов"</string> - <string name="voice_input" msgid="3583258583521397548">"Ключ голосового ввода"</string> + <string name="voice_input" msgid="3583258583521397548">"Кнопка голосового ввода"</string> <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"Значок на основной клавиатуре"</string> <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"Значок на клавиатуре символов"</string> <string name="voice_input_modes_off" msgid="3745699748218082014">"Выкл."</string> diff --git a/java/res/values/donottranslate.xml b/java/res/values/donottranslate.xml index 9e07b2248..8db436bef 100644 --- a/java/res/values/donottranslate.xml +++ b/java/res/values/donottranslate.xml @@ -140,10 +140,12 @@ <string-array name="subtype_locale_exception_keys"> <item>en_US</item> <item>en_GB</item> + <item>es_US</item> </string-array> <string-array name="subtype_locale_exception_values"> <item>English (US)</item> <item>English (UK)</item> + <item>Español (EE.UU.)</item> </string-array> <!-- Generic subtype label --> diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml index dbb56ab4d..4766a2295 100644 --- a/java/res/values/styles.xml +++ b/java/res/values/styles.xml @@ -124,6 +124,7 @@ <item name="keyboardTopPadding">0dp</item> <item name="keyboardBottomPadding">0dp</item> <item name="horizontalGap">0dp</item> + <item name="touchPositionCorrectionData">@null</item> </style> <style name="MoreKeysKeyboardView" @@ -231,6 +232,7 @@ <item name="keyboardTopPadding">0dp</item> <item name="keyboardBottomPadding">0dp</item> <item name="horizontalGap">0dp</item> + <item name="touchPositionCorrectionData">@null</item> </style> <style name="MoreKeysKeyboardView.Stone" @@ -300,6 +302,7 @@ <item name="keyboardTopPadding">0dp</item> <item name="keyboardBottomPadding">0dp</item> <item name="horizontalGap">0dp</item> + <item name="touchPositionCorrectionData">@null</item> </style> <style name="MoreKeysKeyboardView.Gingerbread" @@ -355,6 +358,7 @@ <item name="keyboardTopPadding">0dp</item> <item name="keyboardBottomPadding">0dp</item> <item name="horizontalGap">0dp</item> + <item name="touchPositionCorrectionData">@null</item> </style> <style name="MoreKeysKeyboardView.IceCreamSandwich" diff --git a/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml b/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml index d486b9df0..b11bbba1b 100644 --- a/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml +++ b/java/res/xml-sw600dp/rowkeys_pcqwerty1.xml @@ -52,8 +52,8 @@ latin:keyStyle="hasShiftedLetterHintStyle" /> <Key latin:keyLabel="3" - latin:keyHintLabel="#" - latin:additionalMoreKeys="#" + latin:keyHintLabel="\#" + latin:additionalMoreKeys="\#" latin:moreKeys="!text/more_keys_for_symbols_3" latin:keyStyle="hasShiftedLetterHintStyle" /> <Key diff --git a/java/res/xml-sw600dp/rowkeys_symbols2.xml b/java/res/xml-sw600dp/rowkeys_symbols2.xml index d7067e0cf..7d7dcfe31 100644 --- a/java/res/xml-sw600dp/rowkeys_symbols2.xml +++ b/java/res/xml-sw600dp/rowkeys_symbols2.xml @@ -34,7 +34,7 @@ </case> <default> <Key - latin:keyLabel="#" /> + latin:keyLabel="\#" /> </default> </switch> <Key @@ -49,9 +49,9 @@ <!-- U+066B: "٫" ARABIC DECIMAL SEPARATOR --> <Key latin:keyLabel="٫" - latin:keyHintLabel="#" + latin:keyHintLabel="\#" latin:keyLabelFlags="hasPopupHint|hasShiftedLetterHint" - latin:moreKeys="#" /> + latin:moreKeys="\#" /> </case> <default> <Key diff --git a/java/res/xml-sw600dp/rows_mongolian.xml b/java/res/xml-sw600dp/rows_mongolian.xml new file mode 100644 index 000000000..cfd8c8c39 --- /dev/null +++ b/java/res/xml-sw600dp/rows_mongolian.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <include + latin:keyboardLayout="@xml/key_styles_common" /> + <Row + latin:keyWidth="8.182%p" + > + <include + latin:keyboardLayout="@xml/rowkeys_mongolian1" + latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" /> + <Key + latin:keyStyle="deleteKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <Row + latin:keyWidth="8.182%p" + > + <include + latin:keyboardLayout="@xml/rowkeys_mongolian2" /> + <Key + latin:keyStyle="enterKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <Row + latin:keyWidth="8.182%p" + > + <Key + latin:keyStyle="shiftKeyStyle" + latin:keyWidth="10.0%p" /> + <include + latin:keyboardLayout="@xml/rowkeys_mongolian3" /> + <include + latin:keyboardLayout="@xml/keys_comma_period" /> + </Row> + <include + latin:keyboardLayout="@xml/row_qwerty4" /> +</merge> diff --git a/java/res/xml-sw600dp/rows_number_normal.xml b/java/res/xml-sw600dp/rows_number_normal.xml index f69239456..37bf2e808 100644 --- a/java/res/xml-sw600dp/rows_number_normal.xml +++ b/java/res/xml-sw600dp/rows_number_normal.xml @@ -151,7 +151,7 @@ latin:keyLabel="0" latin:keyStyle="numKeyStyle" /> <Key - latin:keyLabel="#" + latin:keyLabel="\#" latin:keyStyle="numKeyStyle" /> <!-- Note: This Spacer prevents the above key from being marked as a right edge key. --> <Spacer diff --git a/java/res/xml-sw600dp/rows_phone.xml b/java/res/xml-sw600dp/rows_phone.xml index dcc4fde7d..c4799bbcd 100644 --- a/java/res/xml-sw600dp/rows_phone.xml +++ b/java/res/xml-sw600dp/rows_phone.xml @@ -116,7 +116,7 @@ <Key latin:keyStyle="num0KeyStyle" /> <Key - latin:keyLabel="#" + latin:keyLabel="\#" latin:keyStyle="numKeyStyle" /> </Row> </merge> diff --git a/java/res/xml-sw768dp/rows_mongolian.xml b/java/res/xml-sw768dp/rows_mongolian.xml new file mode 100644 index 000000000..9afd4e2db --- /dev/null +++ b/java/res/xml-sw768dp/rows_mongolian.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <include + latin:keyboardLayout="@xml/key_styles_common" /> + <Row + latin:keyWidth="7.375%p" + > + <Key + latin:keyStyle="tabKeyStyle" + latin:keyLabelFlags="alignLeft" + latin:keyWidth="7.969%p" /> + <include + latin:keyboardLayout="@xml/rowkeys_mongolian1" + latin:keyLabelFlags="disableAdditionalMoreKeys|disableKeyHintLabel" /> + <Key + latin:keyStyle="deleteKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <Row + latin:keyWidth="7.227%p" + > + <Key + latin:keyStyle="toSymbolKeyStyle" + latin:keyLabelFlags="alignLeft" + latin:keyWidth="11.172%p" /> + <include + latin:keyboardLayout="@xml/rowkeys_mongolian2" /> + <Key + latin:keyStyle="enterKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <Row + latin:keyWidth="7.000%p" + > + <Key + latin:keyStyle="shiftKeyStyle" + latin:keyWidth="13.829%p" /> + <include + latin:keyboardLayout="@xml/rowkeys_mongolian3" /> + <include + latin:keyboardLayout="@xml/keys_comma_period" /> + <Key + latin:keyStyle="shiftKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <include + latin:keyboardLayout="@xml/row_qwerty4" /> +</merge> diff --git a/java/res/xml-sw768dp/rows_number_normal.xml b/java/res/xml-sw768dp/rows_number_normal.xml index d4d7c722a..de49aba91 100644 --- a/java/res/xml-sw768dp/rows_number_normal.xml +++ b/java/res/xml-sw768dp/rows_number_normal.xml @@ -166,7 +166,7 @@ latin:keyLabel="0" latin:keyStyle="numKeyStyle" /> <Key - latin:keyLabel="#" + latin:keyLabel="\#" latin:keyStyle="numKeyStyle" /> <!-- Note: This Spacer prevents the above key from being marked as a right edge key. --> <Spacer diff --git a/java/res/xml-sw768dp/rows_phone.xml b/java/res/xml-sw768dp/rows_phone.xml index 2f718db62..d06a63b2c 100644 --- a/java/res/xml-sw768dp/rows_phone.xml +++ b/java/res/xml-sw768dp/rows_phone.xml @@ -132,7 +132,7 @@ <Key latin:keyStyle="num0KeyStyle" /> <Key - latin:keyLabel="#" + latin:keyLabel="\#" latin:keyStyle="numKeyStyle" /> </Row> </merge> diff --git a/java/res/xml/kbd_mongolian.xml b/java/res/xml/kbd_mongolian.xml new file mode 100644 index 000000000..1d0352175 --- /dev/null +++ b/java/res/xml/kbd_mongolian.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"): +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<Keyboard + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <include + latin:keyboardLayout="@xml/rows_mongolian" /> +</Keyboard> diff --git a/java/res/xml/key_styles_currency.xml b/java/res/xml/key_styles_currency.xml index 6dea16f89..76fe0e6b0 100644 --- a/java/res/xml/key_styles_currency.xml +++ b/java/res/xml/key_styles_currency.xml @@ -28,7 +28,7 @@ <include latin:keyboardLayout="@xml/key_styles_currency_dollar" /> </case> - <!-- Countries using Euro currency, 23 countries as for January 2011. --> + <!-- Countries using Euro currency, 23 countries as of November 2012. 1. Andorra (ca_AD, ca_ES) 2. Austria (de_AT) 3. Belgium (nl_BE, fr_BE, de_BE) @@ -40,80 +40,77 @@ 9. Greece (el_GR) 10. Ireland (ga_IE, en_IE) 11. Italy (it_IT) - 12. Kosovo (??_XK) ?? + 12. Kosovo (sq_XK, sr_XK) 13. Luxembourg (lb_LU, fr_LU, de_LU) 14. Malta (mt_MT, en_MT) 15. Monaco (fr_MO) - 16. Montenegro (sla_ME) + 16. Montenegro (??_ME) 17. Netherlands (nl_NL) 18. Portugal (pt_PT) 19. San Marino (it_SM) 20. Slovakia (sk_SK) 21. Slovenia (sl_SI) 22. Spain (es_ES, ca_ES) - 23. Vatican City (it_VA) - --> - <!-- Though Denmark and Turkey don't using Euro as their currencies, but having Euro sign on - the symbol keyboard might be useful. Especially Danish krone (kr) and Turkish lira - (TL) can be represented by usual alphabet letters. --> - <!-- Note: Some locales may not have country code, and it it supposed to indicate the - country where the language originally/mainly spoken. --> + 23. Vatican City (it_VA) --> <case - latin:localeCode="da|de|es|el|fi|fr|it|nl|sk|sl|pt_PT|tr" + latin:countryCode="AD|AT|BE|CY|EE|FI|FR|DE|GR|IE|IT|XK|LU|MT|MO|ME|NL|PT|SM|SK|SI|ES|VA" > <include latin:keyboardLayout="@xml/key_styles_currency_euro" /> </case> + <!-- Note: Some subtype locale may not have country code, and it it supposed to indicate the + country where the language originally/mainly spoken. --> + <!-- Though Denmark, Sweden and Turkey don't use Euro as their currency, having the Euro + sign on the symbol keyboard might be useful. Especially Danish krone (kr), Swedish + krona (kr) and Turkish lira (TL) can be represented by usual alphabet letters. --> + <!-- TODO: The currency sign of Turkish Lira was created in 2012 and assigned U+20BA for + its unicode, although there is no font glyph for it as of November 2012. --> + <!-- da: Denmark (da_DK) + de: Germany (de_DE) + es: Spain (es_ES) + fi: Finland (fi_FI) + fr: France(fr_FR) + it: Italy (it_IT) + nl: Netherlands (nl_NL) + sk: Slovakia (sk_SK) + sl: Slovenia (sl_SL) + sv: Sweden (sv_SV) + tr: Trukey (tr_TR) --> <case - latin:languageCode="ca|et|lb|mt|sla" + latin:localeCode="da|de|es|el|fi|fr|it|nl|sk|sl|sv|tr" > <include latin:keyboardLayout="@xml/key_styles_currency_euro" /> </case> + <!-- ca: Catalan (Andorra, Spain) + et: Estonian (Estonia) + lb: Luxembougish (Luxembourg) + mt: Maltese (Malta) --> <case - latin:countryCode="AD|AT|BE|CY|EE|FI|FR|DE|GR|IE|IT|XK|LU|MT|MO|ME|NL|PT|SM|SK|SI|ES|VA" + latin:languageCode="ca|et|lb|mt" > <include latin:keyboardLayout="@xml/key_styles_currency_euro" /> </case> + <!-- fa: Persian (Rial and Afgahni) + hi: Hindi (Indian Rupee) + iw: Hebrew (New Sheqel) + mn: Mongolian (Tugrik) + th: Thai (Baht) + uk: Ukrainian (Hryvnia) + vi: Vietnamese (Dong) --> + <!-- TODO: The currency sign of Turkish Lira was created in 2012 and assigned U+20BA for + its unicode, although there is no font glyph for it as of November 2012. --> <case - latin:languageCode="iw" - > - <!-- U+20AA: "₪" NEW SHEQEL SIGN - U+00A3: "£" POUND SIGN - U+20AC: "€" EURO SIGN - U+00A2: "¢" CENT SIGN --> - <key-style - latin:styleName="currencyKeyStyle" - latin:keyLabel="₪" - latin:moreKeys="!text/more_keys_for_currency_general" /> - <key-style - latin:styleName="moreCurrency1KeyStyle" - latin:keyLabel="£" /> - <key-style - latin:styleName="moreCurrency2KeyStyle" - latin:keyLabel="€" /> - <key-style - latin:styleName="moreCurrency3KeyStyle" - latin:keyLabel="$" - latin:moreKeys="¢" /> - <key-style - latin:styleName="moreCurrency4KeyStyle" - latin:keyLabel="¢" /> - </case> - <case - latin:languageCode="fa" + latin:languageCode="fa|hi|iw|mn|th|uk|vi" > - <!-- U+FDFC: "﷼" RIAL SIGN - U+060B: "؋" AFGHANI SIGN - U+00A3: "£" POUND SIGN + <!-- U+00A3: "£" POUND SIGN U+20AC: "€" EURO SIGN U+00A2: "¢" CENT SIGN --> - <!-- TODO: DroidSansArabic lacks the glyph of U+FCDC: RIAL SIGN --> <key-style latin:styleName="currencyKeyStyle" - latin:keyLabel="﷼" - latin:moreKeys="!text/more_keys_for_currency_general,؋" /> + latin:keyLabel="!text/keylabel_for_currency_generic" + latin:moreKeys="!text/more_keys_for_currency_generic" /> <key-style latin:styleName="moreCurrency1KeyStyle" latin:keyLabel="£" /> @@ -128,18 +125,19 @@ latin:styleName="moreCurrency4KeyStyle" latin:keyLabel="¢" /> </case> - <!-- United Kingdom --> + <!-- GB: United Kingdom (Pound) --> <case latin:countryCode="GB" > <!-- U+00A3: "£" POUND SIGN U+20AC: "€" EURO SIGN U+00A5: "¥" YEN SIGN - U+00A2: "¢" CENT SIGN --> + U+00A2: "¢" CENT SIGN + U+20B1: "₱" PESO SIGN --> <key-style latin:styleName="currencyKeyStyle" latin:keyLabel="£" - latin:moreKeys="!text/more_keys_for_currency_pound" /> + latin:moreKeys="¢,$,€,¥,₱" /> <key-style latin:styleName="moreCurrency1KeyStyle" latin:keyLabel="€" /> @@ -154,6 +152,7 @@ latin:styleName="moreCurrency4KeyStyle" latin:keyLabel="¢" /> </case> + <!-- ar: Arabic (Dollar and Rial) --> <default> <include latin:keyboardLayout="@xml/key_styles_currency_dollar" /> diff --git a/java/res/xml/key_styles_currency_euro.xml b/java/res/xml/key_styles_currency_euro.xml index 686fb3033..c1b5e0384 100644 --- a/java/res/xml/key_styles_currency_euro.xml +++ b/java/res/xml/key_styles_currency_euro.xml @@ -20,13 +20,14 @@ <merge xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"> <!-- U+20AC: "€" EURO SIGN + U+00A2: "¢" CENT SIGN U+00A3: "£" POUND SIGN U+00A5: "¥" YEN SIGN - U+00A2: "¢" CENT SIGN --> + U+20B1: "₱" PESO SIGN --> <key-style latin:styleName="currencyKeyStyle" latin:keyLabel="€" - latin:moreKeys="!text/more_keys_for_currency_euro" /> + latin:moreKeys="¢,£,$,¥,₱" /> <key-style latin:styleName="moreCurrency1KeyStyle" latin:keyLabel="£" /> diff --git a/java/res/xml/keyboard_layout_set_mongolian.xml b/java/res/xml/keyboard_layout_set_mongolian.xml new file mode 100644 index 000000000..2d364f682 --- /dev/null +++ b/java/res/xml/keyboard_layout_set_mongolian.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<KeyboardLayoutSet + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"> + <Element + latin:elementName="alphabet" + latin:elementKeyboard="@xml/kbd_mongolian" + latin:enableProximityCharsCorrection="true" /> + <Element + latin:elementName="symbols" + latin:elementKeyboard="@xml/kbd_symbols" /> + <Element + latin:elementName="symbolsShifted" + latin:elementKeyboard="@xml/kbd_symbols_shift" /> + <Element + latin:elementName="phone" + latin:elementKeyboard="@xml/kbd_phone" /> + <Element + latin:elementName="phoneSymbols" + latin:elementKeyboard="@xml/kbd_phone_symbols" /> + <Element + latin:elementName="number" + latin:elementKeyboard="@xml/kbd_number" /> +</KeyboardLayoutSet> diff --git a/java/res/xml/keys_pcqwerty_symbols1.xml b/java/res/xml/keys_pcqwerty_symbols1.xml index bf48b1f17..2364e1087 100644 --- a/java/res/xml/keys_pcqwerty_symbols1.xml +++ b/java/res/xml/keys_pcqwerty_symbols1.xml @@ -32,7 +32,7 @@ <Key latin:keyLabel="\@" /> <Key - latin:keyLabel="#" /> + latin:keyLabel="\#" /> <Key latin:keyLabel="$" /> <!-- U+2030: "‰" PER MILLE SIGN --> diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml index 038707134..aa59c577c 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -37,6 +37,7 @@ eo: Esperanto/spanish es: Spanish/spanish es_US: Spanish United States/spanish + (es_419: Spanish Latin America/qwerty) et: Estonian/nordic fa: Persian/arabic fi: Finnish/nordic @@ -54,6 +55,7 @@ lt: Lithuanian/qwerty lv: Latvian/qwerty mk: Macedonian/south_slavic + mn: Mongolian/mongolian ms: Malay/qwerty nb: Norwegian Bokmål/nordic nl: Dutch/qwerty @@ -87,161 +89,170 @@ android:isDefault="@bool/im_is_default"> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_en_US" - android:subtypeId="-921088104" + android:subtypeId="0xc9194f98" android:imeSubtypeLocale="en_US" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_en_GB" - android:subtypeId="-1337596075" + android:subtypeId="0xb045e755" android:imeSubtypeLocale="en_GB" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="TrySuppressingImeSwitcher,AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1872175968" + android:subtypeId="0x6f972360" android:imeSubtypeLocale="af" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1494081088" + android:subtypeId="0x590dde40" android:imeSubtypeLocale="ar" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="499361881" + android:subtypeId="0x1dc3a859" android:imeSubtypeLocale="be" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="195674344" + android:subtypeId="0x0ba9c0e8" android:imeSubtypeLocale="bg" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_bulgarian_bds" - android:subtypeId="1599191706" + android:subtypeId="0x5f51ba9a" android:imeSubtypeLocale="bg" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=bulgarian_bds" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-756735787" + android:subtypeId="0xd2e520d5" android:imeSubtypeLocale="ca" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="758984400" + android:subtypeId="0x2d3d2ed0" android:imeSubtypeLocale="cs" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="770990173" + android:subtypeId="0x2df4605d" android:imeSubtypeLocale="da" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="774684257" + android:subtypeId="0x2e2cbe61" android:imeSubtypeLocale="de" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="242746067" + android:subtypeId="0x0e7802d3" android:imeSubtypeLocale="el" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=greek" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1083200842" + android:subtypeId="0x4090554a" android:imeSubtypeLocale="eo" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="816242702" + android:subtypeId="0x30a6e00e" android:imeSubtypeLocale="es" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-2066550842" + android:subtypeId="0x84d2efc6" android:imeSubtypeLocale="es_US" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable" /> + <!-- <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-332580523" + android:subtypeId="0x623f9286" + android:imeSubtypeLocale="es_419" + android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable" + /> + --> + <subtype android:icon="@drawable/ic_subtype_keyboard" + android:label="@string/subtype_generic" + android:subtypeId="0xec2d3955" android:imeSubtypeLocale="et" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=nordic,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1100561836" + android:subtypeId="0xbe66c254" android:imeSubtypeLocale="fa" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=farsi" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="835636643" + android:subtypeId="0x31cecda3" android:imeSubtypeLocale="fi" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="843948332" + android:subtypeId="0x324da12c" android:imeSubtypeLocale="fr" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-354699631" + android:subtypeId="0xeadbb691" android:imeSubtypeLocale="fr_CA" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="963984255" + android:subtypeId="0x39753b7f" android:imeSubtypeLocale="hi" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=hindi" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="901206634" + android:subtypeId="0x35b7526a" android:imeSubtypeLocale="hr" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="903977197" + android:subtypeId="0x35e198ed" android:imeSubtypeLocale="hu" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" @@ -249,21 +260,21 @@ <!-- Java uses the deprecated "in" code instead of the standard "id" code for Indonesian. --> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="2108597344" + android:subtypeId="0x7daea460" android:imeSubtypeLocale="in" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="2113214949" + android:subtypeId="0x7df519e5" android:imeSubtypeLocale="is" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="931682827" + android:subtypeId="0x37885a0b" android:imeSubtypeLocale="it" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" @@ -271,126 +282,133 @@ <!-- Java uses the deprecated "iw" code instead of the standard "he" code for Hebrew. --> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1727731901" + android:subtypeId="0x66fb18bd" android:imeSubtypeLocale="iw" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1846648426" + android:subtypeId="0x6e119e6a" android:imeSubtypeLocale="ka" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=georgian" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="775494660" + android:subtypeId="0x2e391c04" android:imeSubtypeLocale="ky" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-2094941373" + android:subtypeId="0x8321bb43" android:imeSubtypeLocale="lt" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-2093094331" + android:subtypeId="0x833dea45" android:imeSubtypeLocale="lv" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1353667716" + android:subtypeId="0xaf50ab7c" android:imeSubtypeLocale="mk" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=south_slavic" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-2067235743" + android:subtypeId="0xcdcfc3ab" + android:imeSubtypeLocale="mn" + android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="KeyboardLayoutSet=mongolian" + /> + <subtype android:icon="@drawable/ic_subtype_keyboard" + android:label="@string/subtype_generic" + android:subtypeId="0x84c87c61" android:imeSubtypeLocale="ms" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1058205204" + android:subtypeId="0x3f12ee14" android:imeSubtypeLocale="nb" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1067440414" + android:subtypeId="0x3f9fd91e" android:imeSubtypeLocale="nl" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1343007020" + android:subtypeId="0x500ca92c" android:imeSubtypeLocale="nl_BE" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=azerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1124698716" + android:subtypeId="0x43098a5c" android:imeSubtypeLocale="pl" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-889195354" + android:subtypeId="0xcafff4a6" android:imeSubtypeLocale="pt_BR" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-486540198" + android:subtypeId="0xe2fffc5a" android:imeSubtypeLocale="pt_PT" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1927784072" + android:subtypeId="0x8d185978" android:imeSubtypeLocale="ro" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1983547218" + android:subtypeId="0x763a8752" android:imeSubtypeLocale="ru" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1902849005" + android:subtypeId="0x8e94d413" android:imeSubtypeLocale="sk" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1901925484" + android:subtypeId="0x8ea2eb94" android:imeSubtypeLocale="sl" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="2009405806" + android:subtypeId="0x77c5196e" android:imeSubtypeLocale="sr" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="SupportTouchPositionCorrection" @@ -398,14 +416,14 @@ <!-- TODO: Uncomment once we can handle IETF language tag with script name specified. <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_serbian_cyrillic" - android:subtypeId="XXXXXX" + android:subtypeId="0xXXXXXXXX" android:imeSubtypeLocale="sr" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_serbian_latin" - android:subtypeId="XXXXXX" + android:subtypeId="0xXXXXXXXX" android:imeSubtypeLocale="sr-Latn" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" @@ -413,63 +431,63 @@ --> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1219821379" + android:subtypeId="0x48b4ff43" android:imeSubtypeLocale="sv" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1891766753" + android:subtypeId="0x8f3dee1f" android:imeSubtypeLocale="sw" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="529847764" + android:subtypeId="0x1f94d5d4" android:imeSubtypeLocale="th" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=thai" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-259881489" + android:subtypeId="0xf08285ef" android:imeSubtypeLocale="tl" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=spanish,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1244756446" + android:subtypeId="0x4a3179de" android:imeSubtypeLocale="tr" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="1048856876" + android:subtypeId="0x3e84492c" android:imeSubtypeLocale="uk" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1818808594" + android:subtypeId="0x93972eee" android:imeSubtypeLocale="vi" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" - android:subtypeId="-1693209738" + android:subtypeId="0x9b13ab76" android:imeSubtypeLocale="zu" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_no_language_qwerty" - android:subtypeId="-1573262419" + android:subtypeId="0xa239ebad" android:imeSubtypeLocale="zz" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable" diff --git a/java/res/xml/rowkeys_mongolian1.xml b/java/res/xml/rowkeys_mongolian1.xml new file mode 100644 index 000000000..6c8c8e2fd --- /dev/null +++ b/java/res/xml/rowkeys_mongolian1.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <!-- U+0444: "ф" CYRILLIC SMALL LETTER EF --> + <Key + latin:keyLabel="ф" + latin:keyHintLabel="1" + latin:additionalMoreKeys="1" /> + <!-- U+0446: "ц" CYRILLIC SMALL LETTER TSE --> + <Key + latin:keyLabel="ц" + latin:keyHintLabel="2" + latin:additionalMoreKeys="2" /> + <!-- U+0443: "у" CYRILLIC SMALL LETTER U --> + <Key + latin:keyLabel="у" + latin:keyHintLabel="3" + latin:additionalMoreKeys="3" + latin:moreKeys="!text/more_keys_for_cyrillic_u" /> + <!-- U+0436: "ж" CYRILLIC SMALL LETTER ZHE --> + <Key + latin:keyLabel="ж" + latin:keyHintLabel="4" + latin:additionalMoreKeys="4" /> + <!-- U+044D: "э" CYRILLIC SMALL LETTER E --> + <Key + latin:keyLabel="э" + latin:keyHintLabel="5" + latin:additionalMoreKeys="5" + latin:moreKeys="!text/more_keys_for_cyrillic_ie" /> + <!-- U+043D: "н" CYRILLIC SMALL LETTER EN --> + <Key + latin:keyLabel="н" + latin:keyHintLabel="6" + latin:additionalMoreKeys="6" + latin:moreKeys="!text/more_keys_for_cyrillic_en" /> + <!-- U+0433: "г" CYRILLIC SMALL LETTER GHE --> + <Key + latin:keyLabel="г" + latin:keyHintLabel="7" + latin:additionalMoreKeys="7" + latin:moreKeys="!text/more_keys_for_cyrillic_ghe" /> + <!-- U+0448: "ш" CYRILLIC SMALL LETTER SHA + U+0449: "щ" CYRILLIC SMALL LETTER SHCHA --> + <Key + latin:keyLabel="ш" + latin:keyHintLabel="8" + latin:additionalMoreKeys="8" + latin:moreKeys="щ" /> + <!-- U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U --> + <Key + latin:keyLabel="ү" + latin:keyHintLabel="9" + latin:additionalMoreKeys="9" /> + <!-- U+0437: "з" CYRILLIC SMALL LETTER ZE --> + <Key + latin:keyLabel="з" + latin:keyHintLabel="0" + latin:additionalMoreKeys="0" /> + <!-- U+043A: "к" CYRILLIC SMALL LETTER KA --> + <Key + latin:keyLabel="к" /> +</merge> diff --git a/java/res/xml/rowkeys_mongolian2.xml b/java/res/xml/rowkeys_mongolian2.xml new file mode 100644 index 000000000..a8aa00620 --- /dev/null +++ b/java/res/xml/rowkeys_mongolian2.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <!-- U+0439: "й" CYRILLIC SMALL LETTER SHORT I --> + <Key + latin:keyLabel="й" /> + <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU --> + <Key + latin:keyLabel="ы" /> + <!-- U+0431: "б" CYRILLIC SMALL LETTER BE --> + <Key + latin:keyLabel="б" /> + <!-- U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O --> + <Key + latin:keyLabel="ө" /> + <!-- U+0430: "а" CYRILLIC SMALL LETTER A --> + <Key + latin:keyLabel="а" /> + <!-- U+0445: "х" CYRILLIC SMALL LETTER HA --> + <Key + latin:keyLabel="х" /> + <!-- U+0440: "р" CYRILLIC SMALL LETTER ER --> + <Key + latin:keyLabel="р" /> + <!-- U+043E: "о" CYRILLIC SMALL LETTER O --> + <Key + latin:keyLabel="о" /> + <!-- U+043B: "л" CYRILLIC SMALL LETTER EL --> + <Key + latin:keyLabel="л" /> + <!-- U+0434: "д" CYRILLIC SMALL LETTER DE --> + <Key + latin:keyLabel="д" /> + <!-- U+043F: "п" CYRILLIC SMALL LETTER PE --> + <Key + latin:keyLabel="п" /> +</merge> diff --git a/java/res/xml/rowkeys_mongolian3.xml b/java/res/xml/rowkeys_mongolian3.xml new file mode 100644 index 000000000..dc80c37ab --- /dev/null +++ b/java/res/xml/rowkeys_mongolian3.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <!-- U+044F: "я" CYRILLIC SMALL LETTER YA --> + <Key + latin:keyLabel="я" /> + <!-- U+0447: "ч" CYRILLIC SMALL LETTER CHE --> + <Key + latin:keyLabel="ч" /> + <!-- U+0451: "ё" CYRILLIC SMALL LETTER IO + U+0435: "е" CYRILLIC SMALL LETTER IE --> + <Key + latin:keyLabel="ё" + latin:moreKeys="е" /> + <!-- U+0441: "с" CYRILLIC SMALL LETTER ES --> + <Key + latin:keyLabel="с" /> + <!-- U+043C: "м" CYRILLIC SMALL LETTER EM --> + <Key + latin:keyLabel="м" /> + <!-- U+0438: "и" CYRILLIC SMALL LETTER I --> + <Key + latin:keyLabel="и" /> + <!-- U+0442: "т" CYRILLIC SMALL LETTER TE --> + <Key + latin:keyLabel="т" /> + <!-- U+044C: "ь" CYRILLIC SMALL LETTER SOFT SIGN + U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN --> + <Key + latin:keyLabel="ь" + latin:moreKeys="ъ" /> + <!-- U+0432: "в" CYRILLIC SMALL LETTER VE + U+044E: "ю" CYRILLIC SMALL LETTER YU --> + <Key + latin:keyLabel="в" + latin:moreKeys="ю" /> +</merge> diff --git a/java/res/xml/rowkeys_pcqwerty1.xml b/java/res/xml/rowkeys_pcqwerty1.xml index 0ecda3550..b2d1d374b 100644 --- a/java/res/xml/rowkeys_pcqwerty1.xml +++ b/java/res/xml/rowkeys_pcqwerty1.xml @@ -41,7 +41,7 @@ latin:moreKeys="!text/more_keys_for_symbols_2" /> <Key latin:keyLabel="3" - latin:additionalMoreKeys="#" + latin:additionalMoreKeys="\#" latin:moreKeys="!text/more_keys_for_symbols_3" /> <Key latin:keyLabel="4" diff --git a/java/res/xml/rowkeys_symbols2.xml b/java/res/xml/rowkeys_symbols2.xml index 425e20470..d3c1278da 100644 --- a/java/res/xml/rowkeys_symbols2.xml +++ b/java/res/xml/rowkeys_symbols2.xml @@ -33,14 +33,14 @@ latin:moreKeys="\@" /> <Key latin:keyLabel="٫" - latin:keyHintLabel="#" - latin:moreKeys="#" /> + latin:keyHintLabel="\#" + latin:moreKeys="\#" /> </case> <default> <Key latin:keyLabel="\@" /> <Key - latin:keyLabel="#" /> + latin:keyLabel="\#" /> </default> </switch> <Key diff --git a/java/res/xml/rows_mongolian.xml b/java/res/xml/rows_mongolian.xml new file mode 100644 index 000000000..a6a890a9b --- /dev/null +++ b/java/res/xml/rows_mongolian.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<merge + xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin" +> + <include + latin:keyboardLayout="@xml/key_styles_common" /> + <Row + latin:keyWidth="9.091%p" + > + <include + latin:keyboardLayout="@xml/rowkeys_mongolian1" /> + </Row> + <Row + latin:keyWidth="9.091%p" + > + <include + latin:keyboardLayout="@xml/rowkeys_mongolian2" /> + </Row> + <Row + latin:keyWidth="8.711%p" + > + <Key + latin:keyStyle="shiftKeyStyle" + latin:keyWidth="10.8%p" /> + <include + latin:keyboardLayout="@xml/rowkeys_mongolian3" /> + <Key + latin:keyStyle="deleteKeyStyle" + latin:keyWidth="fillRight" /> + </Row> + <include + latin:keyboardLayout="@xml/row_qwerty4" /> +</merge> diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml index c59e26247..b77544bc3 100644 --- a/java/res/xml/rows_number_normal.xml +++ b/java/res/xml/rows_number_normal.xml @@ -33,6 +33,8 @@ latin:keyStyle="numKeyStyle" /> <Key latin:keyLabel="-" + latin:moreKeys="+" + latin:keyLabelFlags="hasPopupHint" latin:keyStyle="numFunctionalKeyStyle" latin:keyWidth="fillRight" /> </Row> diff --git a/java/res/xml/rows_phone.xml b/java/res/xml/rows_phone.xml index 630b24ea4..9299c2aa5 100644 --- a/java/res/xml/rows_phone.xml +++ b/java/res/xml/rows_phone.xml @@ -34,6 +34,8 @@ latin:keyStyle="num3KeyStyle" /> <Key latin:keyLabel="-" + latin:moreKeys="+" + latin:keyLabelFlags="hasPopupHint" latin:keyStyle="numFunctionalKeyStyle" latin:keyWidth="fillRight" /> </Row> diff --git a/java/res/xml/rows_phone_symbols.xml b/java/res/xml/rows_phone_symbols.xml index 7841c56e5..8c10a2d71 100644 --- a/java/res/xml/rows_phone_symbols.xml +++ b/java/res/xml/rows_phone_symbols.xml @@ -37,6 +37,8 @@ latin:keyStyle="numKeyStyle" /> <Key latin:keyLabel="-" + latin:moreKeys="+" + latin:keyLabelFlags="hasPopupHint" latin:keyStyle="numFunctionalKeyStyle" latin:keyWidth="fillRight" /> </Row> @@ -63,7 +65,7 @@ <Key latin:keyStyle="numWaitKeyStyle" /> <Key - latin:keyLabel="#" + latin:keyLabel="\#" latin:keyStyle="numKeyStyle" /> <Key latin:keyStyle="deleteKeyStyle" diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 32618ad85..b87ae3a15 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -26,6 +26,7 @@ import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import java.util.HashMap; @@ -61,17 +62,19 @@ public final class KeyCodeDescriptionMapper { mKeyLabelMap.put(":-)", R.string.spoken_description_smiley); // Special non-character codes defined in Keyboard - mKeyCodeMap.put(Keyboard.CODE_SPACE, R.string.spoken_description_space); - mKeyCodeMap.put(Keyboard.CODE_DELETE, R.string.spoken_description_delete); - mKeyCodeMap.put(Keyboard.CODE_ENTER, R.string.spoken_description_return); - mKeyCodeMap.put(Keyboard.CODE_SETTINGS, R.string.spoken_description_settings); - mKeyCodeMap.put(Keyboard.CODE_SHIFT, R.string.spoken_description_shift); - mKeyCodeMap.put(Keyboard.CODE_SHORTCUT, R.string.spoken_description_mic); - mKeyCodeMap.put(Keyboard.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); - mKeyCodeMap.put(Keyboard.CODE_TAB, R.string.spoken_description_tab); - mKeyCodeMap.put(Keyboard.CODE_LANGUAGE_SWITCH, R.string.spoken_description_language_switch); - mKeyCodeMap.put(Keyboard.CODE_ACTION_NEXT, R.string.spoken_description_action_next); - mKeyCodeMap.put(Keyboard.CODE_ACTION_PREVIOUS, R.string.spoken_description_action_previous); + mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); + mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); + mKeyCodeMap.put(Constants.CODE_ENTER, R.string.spoken_description_return); + mKeyCodeMap.put(Constants.CODE_SETTINGS, R.string.spoken_description_settings); + mKeyCodeMap.put(Constants.CODE_SHIFT, R.string.spoken_description_shift); + mKeyCodeMap.put(Constants.CODE_SHORTCUT, R.string.spoken_description_mic); + mKeyCodeMap.put(Constants.CODE_SWITCH_ALPHA_SYMBOL, R.string.spoken_description_to_symbol); + mKeyCodeMap.put(Constants.CODE_TAB, R.string.spoken_description_tab); + mKeyCodeMap.put(Constants.CODE_LANGUAGE_SWITCH, + R.string.spoken_description_language_switch); + mKeyCodeMap.put(Constants.CODE_ACTION_NEXT, R.string.spoken_description_action_next); + mKeyCodeMap.put(Constants.CODE_ACTION_PREVIOUS, + R.string.spoken_description_action_previous); } /** @@ -97,17 +100,17 @@ public final class KeyCodeDescriptionMapper { boolean shouldObscure) { final int code = key.mCode; - if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { final String description = getDescriptionForSwitchAlphaSymbol(context, keyboard); if (description != null) return description; } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { return getDescriptionForShiftKey(context, keyboard); } - if (code == Keyboard.CODE_ACTION_ENTER) { + if (code == Constants.CODE_ACTION_ENTER) { return getDescriptionForActionKey(context, keyboard, key); } @@ -121,7 +124,7 @@ public final class KeyCodeDescriptionMapper { } // Just attempt to speak the description. - if (key.mCode != Keyboard.CODE_UNSPECIFIED) { + if (key.mCode != Constants.CODE_UNSPECIFIED) { return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); } diff --git a/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java b/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java new file mode 100644 index 000000000..ea5f12ce2 --- /dev/null +++ b/java/src/com/android/inputmethod/annotations/ExternallyReferenced.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.annotations; + +/** + * Denotes that the class, method or field should not be eliminated by ProGuard, + * because it is externally referenced. (See proguard.flags) + */ +public @interface ExternallyReferenced { +} diff --git a/java/src/com/android/inputmethod/annotations/UsedForTesting.java b/java/src/com/android/inputmethod/annotations/UsedForTesting.java new file mode 100644 index 000000000..2ada091e4 --- /dev/null +++ b/java/src/com/android/inputmethod/annotations/UsedForTesting.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.annotations; + +/** + * Denotes that the class, method or field should not be eliminated by ProGuard, + * so that unit tests can access it. (See proguard.flags) + */ +public @interface UsedForTesting { +} diff --git a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java index ab7bd4914..8bd1e5208 100644 --- a/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java +++ b/java/src/com/android/inputmethod/compat/InputMethodManagerCompatWrapper.java @@ -19,61 +19,21 @@ package com.android.inputmethod.compat; import android.content.Context; import android.os.IBinder; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import com.android.inputmethod.latin.ImfUtils; import java.lang.reflect.Method; -// TODO: Override this class with the concrete implementation if we need to take care of the -// performance. public final class InputMethodManagerCompatWrapper { - private static final String TAG = InputMethodManagerCompatWrapper.class.getSimpleName(); private static final Method METHOD_switchToNextInputMethod = CompatUtils.getMethod( InputMethodManager.class, "switchToNextInputMethod", IBinder.class, Boolean.TYPE); - private static final InputMethodManagerCompatWrapper sInstance = - new InputMethodManagerCompatWrapper(); - - private InputMethodManager mImm; - - private InputMethodManagerCompatWrapper() { - // This wrapper class is not publicly instantiable. - } - - public static InputMethodManagerCompatWrapper getInstance() { - if (sInstance.mImm == null) { - throw new RuntimeException(TAG + ".getInstance() is called before initialization"); - } - return sInstance; - } - - public static void init(Context context) { - sInstance.mImm = ImfUtils.getInputMethodManager(context); - } - - public InputMethodSubtype getCurrentInputMethodSubtype() { - return mImm.getCurrentInputMethodSubtype(); - } - - public InputMethodSubtype getLastInputMethodSubtype() { - return mImm.getLastInputMethodSubtype(); - } + public final InputMethodManager mImm; - public boolean switchToLastInputMethod(IBinder token) { - return mImm.switchToLastInputMethod(token); + public InputMethodManagerCompatWrapper(final Context context) { + mImm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); } - public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { return (Boolean)CompatUtils.invoke(mImm, false, METHOD_switchToNextInputMethod, token, onlyCurrentIme); } - - public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { - mImm.setInputMethodAndSubtype(token, id, subtype); - } - - public void showInputMethodPicker() { - mImm.showInputMethodPicker(); - } } diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index 159f43650..c1609ea28 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -83,14 +83,13 @@ public final class SuggestionSpanUtils { } public static CharSequence getTextWithAutoCorrectionIndicatorUnderline( - Context context, CharSequence text) { + final Context context, final String text) { if (TextUtils.isEmpty(text) || CONSTRUCTOR_SuggestionSpan == null || OBJ_FLAG_AUTO_CORRECTION == null || OBJ_SUGGESTIONS_MAX_SIZE == null || OBJ_FLAG_MISSPELLED == null || OBJ_FLAG_EASY_CORRECT == null) { return text; } - final Spannable spannable = text instanceof Spannable - ? (Spannable) text : new SpannableString(text); + final Spannable spannable = new SpannableString(text); final Object[] args = { context, null, new String[] {}, (int)OBJ_FLAG_AUTO_CORRECTION, (Class<?>) SuggestionSpanPickedNotificationReceiver.class }; @@ -104,28 +103,24 @@ public final class SuggestionSpanUtils { return spannable; } - public static CharSequence getTextWithSuggestionSpan(Context context, - CharSequence pickedWord, SuggestedWords suggestedWords, boolean dictionaryAvailable) { + public static CharSequence getTextWithSuggestionSpan(final Context context, + final String pickedWord, final SuggestedWords suggestedWords, + final boolean dictionaryAvailable) { if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) || CONSTRUCTOR_SuggestionSpan == null - || suggestedWords == null || suggestedWords.size() == 0 - || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions + || suggestedWords.isEmpty() || suggestedWords.mIsPrediction + || suggestedWords.mIsPunctuationSuggestions || OBJ_SUGGESTIONS_MAX_SIZE == null) { return pickedWord; } - final Spannable spannable; - if (pickedWord instanceof Spannable) { - spannable = (Spannable) pickedWord; - } else { - spannable = new SpannableString(pickedWord); - } + final Spannable spannable = new SpannableString(pickedWord); final ArrayList<String> suggestionsList = CollectionUtils.newArrayList(); for (int i = 0; i < suggestedWords.size(); ++i) { if (suggestionsList.size() >= OBJ_SUGGESTIONS_MAX_SIZE) { break; } - final CharSequence word = suggestedWords.getWord(i); + final String word = suggestedWords.getWord(i); if (!TextUtils.equals(pickedWord, word)) { suggestionsList.add(word.toString()); } diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 30812e8c3..7346a9c38 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -16,11 +16,11 @@ package com.android.inputmethod.keyboard; -import static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT; -import static com.android.inputmethod.keyboard.Keyboard.CODE_SHIFT; -import static com.android.inputmethod.keyboard.Keyboard.CODE_SWITCH_ALPHA_SYMBOL; -import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_SHIFT; +import static com.android.inputmethod.latin.Constants.CODE_SWITCH_ALPHA_SYMBOL; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.content.res.Resources; import android.content.res.TypedArray; @@ -39,6 +39,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeyboardRow; import com.android.inputmethod.keyboard.internal.MoreKeySpec; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.StringUtils; @@ -453,7 +454,7 @@ public class Key implements Comparable<Key> { label = "/" + mLabel; } return String.format("%s%s %d,%d %dx%d %s/%s/%s", - Keyboard.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, + Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); } diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java index aa683c1d7..0a91284d0 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java +++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java @@ -118,7 +118,7 @@ public class KeyDetector { } public static String printableCode(Key key) { - return key != null ? Keyboard.printableCode(key.mCode) : "none"; + return key != null ? Constants.printableCode(key.mCode) : "none"; } public static String printableCodes(int[] codes) { @@ -127,7 +127,7 @@ public class KeyDetector { for (final int code : codes) { if (code == Constants.NOT_A_CODE) break; if (addDelimiter) sb.append(", "); - sb.append(Keyboard.printableCode(code)); + sb.append(Constants.printableCode(code)); addDelimiter = true; } return "[" + sb + "]"; diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index b7c7f415d..a1b1f5dad 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -16,15 +16,13 @@ package com.android.inputmethod.keyboard; -import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.CollectionUtils; - - +import com.android.inputmethod.latin.Constants; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard @@ -45,46 +43,6 @@ import com.android.inputmethod.latin.CollectionUtils; * </pre> */ public class Keyboard { - private static final String TAG = Keyboard.class.getSimpleName(); - - /** Some common keys code. Must be positive. - * These should be aligned with values/keycodes.xml - */ - public static final int CODE_ENTER = '\n'; - public static final int CODE_TAB = '\t'; - public static final int CODE_SPACE = ' '; - public static final int CODE_PERIOD = '.'; - public static final int CODE_DASH = '-'; - public static final int CODE_SINGLE_QUOTE = '\''; - public static final int CODE_DOUBLE_QUOTE = '"'; - public static final int CODE_QUESTION_MARK = '?'; - public static final int CODE_EXCLAMATION_MARK = '!'; - // TODO: Check how this should work for right-to-left languages. It seems to stand - // that for rtl languages, a closing parenthesis is a left parenthesis. Is this - // managed by the font? Or is it a different char? - public static final int CODE_CLOSING_PARENTHESIS = ')'; - public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; - public static final int CODE_CLOSING_CURLY_BRACKET = '}'; - public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; - - /** Special keys code. Must be negative. - * These should be aligned with KeyboardCodesSet.ID_TO_NAME[], - * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[] - */ - public static final int CODE_SHIFT = -1; - public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; - public static final int CODE_OUTPUT_TEXT = -3; - public static final int CODE_DELETE = -4; - public static final int CODE_SETTINGS = -5; - public static final int CODE_SHORTCUT = -6; - public static final int CODE_ACTION_ENTER = -7; - public static final int CODE_ACTION_NEXT = -8; - public static final int CODE_ACTION_PREVIOUS = -9; - public static final int CODE_LANGUAGE_SWITCH = -10; - public static final int CODE_RESEARCH = -11; - // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -12; - public final KeyboardId mId; public final int mThemeId; @@ -163,7 +121,7 @@ public class Keyboard { } public Key getKey(final int code) { - if (code == CODE_UNSPECIFIED) { + if (code == Constants.CODE_UNSPECIFIED) { return null; } synchronized (mKeyCache) { @@ -197,10 +155,6 @@ public class Keyboard { return false; } - public static boolean isLetterCode(final int code) { - return code >= CODE_SPACE; - } - @Override public String toString() { return mId.toString(); @@ -219,27 +173,4 @@ public class Keyboard { final int adjustedY = Math.max(0, Math.min(y, mOccupiedHeight - 1)); return mProximityInfo.getNearestKeys(adjustedX, adjustedY); } - - public static String printableCode(final int code) { - switch (code) { - case CODE_SHIFT: return "shift"; - case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; - case CODE_OUTPUT_TEXT: return "text"; - case CODE_DELETE: return "delete"; - case CODE_SETTINGS: return "settings"; - case CODE_SHORTCUT: return "shortcut"; - case CODE_ACTION_ENTER: return "actionEnter"; - case CODE_ACTION_NEXT: return "actionNext"; - case CODE_ACTION_PREVIOUS: return "actionPrevious"; - case CODE_LANGUAGE_SWITCH: return "languageSwitch"; - case CODE_UNSPECIFIED: return "unspec"; - case CODE_TAB: return "tab"; - case CODE_ENTER: return "enter"; - default: - if (code <= 0) Log.w(TAG, "Unknown non-positive key code=" + code); - if (code < CODE_SPACE) return String.format("'\\u%02x'", code); - if (code < 0x100) return String.format("'%c'", code); - return String.format("'\\u%04x'", code); - } - } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java index 5c8f78f5e..b612f0927 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java @@ -56,11 +56,11 @@ public interface KeyboardActionListener { public void onCodeInput(int primaryCode, int x, int y); /** - * Sends a sequence of characters to the listener. + * Sends a string of characters to the listener. * - * @param text the sequence of characters to be displayed. + * @param text the string of characters to be registered. */ - public void onTextInput(CharSequence text); + public void onTextInput(String text); /** * Called when user started batch input. @@ -99,7 +99,7 @@ public interface KeyboardActionListener { @Override public void onCodeInput(int primaryCode, int x, int y) {} @Override - public void onTextInput(CharSequence text) {} + public void onTextInput(String text) {} @Override public void onStartBatchInput() {} @Override @@ -112,13 +112,5 @@ public interface KeyboardActionListener { public boolean onCustomRequest(int requestCode) { return false; } - - // TODO: Remove this method when the vertical correction is removed. - public static boolean isInvalidCoordinate(int coordinate) { - // Detect {@link Constants#NOT_A_COORDINATE}, - // {@link Constants#SUGGESTION_STRIP_COORDINATE}, and - // {@link Constants#SPELL_CHECKER_COORDINATE}. - return coordinate < 0; - } } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index c7813ab02..4d5d7e14e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -34,6 +34,7 @@ import android.util.Xml; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.EditorInfoCompatUtils; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -194,12 +195,11 @@ public final class KeyboardLayoutSet { final Params params = mParams; final boolean isSymbols = (keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS || keyboardLayoutSetElementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED); - final boolean noLanguage = SubtypeLocale.isNoLanguage(params.mSubtype); - final boolean voiceKeyEnabled = params.mVoiceKeyEnabled && !noLanguage; - final boolean hasShortcutKey = voiceKeyEnabled && (isSymbols != params.mVoiceKeyOnMain); + final boolean hasShortcutKey = params.mVoiceKeyEnabled + && (isSymbols != params.mVoiceKeyOnMain); return new KeyboardId(keyboardLayoutSetElementId, params.mSubtype, params.mDeviceFormFactor, params.mOrientation, params.mWidth, params.mMode, params.mEditorInfo, - params.mNoSettingsKey, voiceKeyEnabled, hasShortcutKey, + params.mNoSettingsKey, params.mVoiceKeyEnabled, hasShortcutKey, params.mLanguageSwitchKeyEnabled); } @@ -266,7 +266,7 @@ public final class KeyboardLayoutSet { return this; } - // For test only + @UsedForTesting public void disableTouchPositionCorrectionDataForTest() { mParams.mDisableTouchPositionCorrectionDataForTest = true; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index de8097b55..c2e31ee1e 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -29,12 +29,12 @@ import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyboardState; -import com.android.inputmethod.latin.DebugSettings; -import com.android.inputmethod.latin.ImfUtils; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SettingsValues; import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.WordComposer; @@ -65,9 +65,9 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { new KeyboardTheme(5, R.style.KeyboardTheme_IceCreamSandwich), }; + private AudioAndHapticFeedbackManager mFeedbackManager; private SubtypeSwitcher mSubtypeSwitcher; private SharedPreferences mPrefs; - private boolean mForceNonDistinctMultitouch; private InputView mCurrentInputView; private MainKeyboardView mKeyboardView; @@ -102,12 +102,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private void initInternal(LatinIME latinIme, SharedPreferences prefs) { mLatinIME = latinIme; mResources = latinIme.getResources(); + mFeedbackManager = new AudioAndHapticFeedbackManager(latinIme); mPrefs = prefs; mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mState = new KeyboardState(this); setContextThemeWrapper(latinIme, getKeyboardTheme(latinIme, prefs)); - mForceNonDistinctMultitouch = prefs.getBoolean( - DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); } private static KeyboardTheme getKeyboardTheme(Context context, SharedPreferences prefs) { @@ -143,10 +142,11 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { builder.setOptions( settingsValues.isVoiceKeyEnabled(editorInfo), settingsValues.isVoiceKeyOnMain(), - settingsValues.isLanguageSwitchKeyEnabled(mThemeContext)); + settingsValues.isLanguageSwitchKeyEnabled()); mKeyboardLayoutSet = builder.build(); try { mState.onLoadKeyboard(mResources.getString(R.string.layout_switch_back_symbols)); + mFeedbackManager.onSettingsChanged(settingsValues); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); @@ -154,6 +154,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } + public void onRingerModeChanged() { + mFeedbackManager.onRingerModeChanged(); + } + public void saveKeyboardState() { if (getKeyboard() != null) { mState.onSaveKeyboardState(); @@ -183,7 +187,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { final boolean needsToDisplayLanguage = mSubtypeSwitcher.needsToDisplayLanguage( keyboard.mId.mLocale); keyboardView.startDisplayLanguageOnSpacebar(subtypeChanged, needsToDisplayLanguage, - ImfUtils.hasMultipleEnabledIMEsOrSubtypes(mLatinIME, true)); + RichInputMethodManager.getInstance().hasMultipleEnabledIMEsOrSubtypes(true)); } public Keyboard getKeyboard() { @@ -208,7 +212,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { public void onPressKey(int code) { if (isVibrateAndSoundFeedbackRequired()) { - mLatinIME.hapticAndAudioFeedback(code); + mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); } mState.onPressKey(code, isSinglePointer(), mLatinIME.getCurrentAutoCapsState()); } @@ -320,7 +324,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // Implements {@link KeyboardState.SwitchActions}. @Override public void hapticAndAudioFeedback(int code) { - mLatinIME.hapticAndAudioFeedback(code); + mFeedbackManager.hapticAndAudioFeedback(code, mKeyboardView); } public void onLongPressTimeout(int code) { @@ -339,10 +343,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return mKeyboardView != null && mKeyboardView.getPointerCount() == 1; } - public boolean hasDistinctMultitouch() { - return mKeyboardView != null && mKeyboardView.hasDistinctMultitouch(); - } - /** * Updates state machine to figure out when to automatically switch back to the previous mode. */ @@ -369,9 +369,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off? } mKeyboardView.setKeyboardActionListener(mLatinIME); - if (mForceNonDistinctMultitouch) { - mKeyboardView.setDistinctMultitouch(false); - } // This always needs to be set since the accessibility state can // potentially change without the input view being re-created. diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 3e6f92c2a..d5f40ad36 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard; import android.animation.AnimatorInflater; import android.animation.ObjectAnimator; import android.content.Context; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.TypedArray; @@ -28,7 +29,7 @@ import android.graphics.Paint.Align; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Message; -import android.text.TextUtils; +import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; @@ -41,11 +42,13 @@ import android.widget.PopupWindow; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.annotations.ExternallyReferenced; import com.android.inputmethod.keyboard.PointerTracker.DrawingProxy; import com.android.inputmethod.keyboard.PointerTracker.TimerProxy; import com.android.inputmethod.keyboard.internal.KeyDrawParams; -import com.android.inputmethod.keyboard.internal.SuddenJumpingTouchEventHandler; +import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.DebugSettings; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -95,7 +98,7 @@ import java.util.WeakHashMap; * @attr ref R.styleable#MainKeyboardView_suppressKeyPreviewAfterBatchInputDuration */ public final class MainKeyboardView extends KeyboardView implements PointerTracker.KeyEventHandler, - SuddenJumpingTouchEventHandler.ProcessMotionEvent { + TouchScreenRegulator.ProcessMotionEvent { private static final String TAG = MainKeyboardView.class.getSimpleName(); // TODO: Kill process when the usability study mode was changed. @@ -138,10 +141,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack new WeakHashMap<Key, MoreKeysPanel>(); private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; - private final SuddenJumpingTouchEventHandler mTouchScreenRegulator; + private final TouchScreenRegulator mTouchScreenRegulator; protected KeyDetector mKeyDetector; - private boolean mHasDistinctMultitouch; + private final boolean mHasDistinctMultitouch; private int mOldPointerCount = 1; private Key mOldKey; @@ -203,7 +206,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private void startKeyRepeatTimer(final PointerTracker tracker, final long delay) { final Key key = tracker.getKey(); - if (key == null) return; + if (key == null) { + return; + } sendMessageDelayed(obtainMessage(MSG_REPEAT_KEY, key.mCode, 0, tracker), delay); } @@ -226,7 +231,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack cancelLongPressTimer(); final int delay; switch (code) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: delay = mLongPressShiftKeyTimeout; break; default: @@ -247,7 +252,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final Key key = tracker.getKey(); final int delay; switch (key.mCode) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: delay = mLongPressShiftKeyTimeout; break; default: @@ -304,7 +309,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // When user hits the space or the enter key, just cancel the while-typing timer. final int typedCode = typedKey.mCode; - if (typedCode == Keyboard.CODE_SPACE || typedCode == Keyboard.CODE_ENTER) { + if (typedCode == Constants.CODE_SPACE || typedCode == Constants.CODE_ENTER) { startWhileTypingFadeinAnimation(keyboardView); return; } @@ -356,15 +361,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public MainKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - mTouchScreenRegulator = new SuddenJumpingTouchEventHandler(getContext(), this); + mTouchScreenRegulator = new TouchScreenRegulator(context, this); - mHasDistinctMultitouch = context.getPackageManager() + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final boolean forceNonDistinctMultitouch = prefs.getBoolean( + DebugSettings.FORCE_NON_DISTINCT_MULTITOUCH_KEY, false); + final boolean hasDistinctMultitouch = context.getPackageManager() .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); + mHasDistinctMultitouch = hasDistinctMultitouch && !forceNonDistinctMultitouch; final Resources res = getResources(); final boolean needsPhantomSuddenMoveEventHack = Boolean.parseBoolean( ResourceUtils.getDeviceOverrideValue(res, R.array.phantom_sudden_move_event_device_list, "false")); - PointerTracker.init(mHasDistinctMultitouch, needsPhantomSuddenMoveEventHack); + PointerTracker.init(needsPhantomSuddenMoveEventHack); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.MainKeyboardView, defStyle, R.style.MainKeyboardView); @@ -408,7 +417,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } private ObjectAnimator loadObjectAnimator(final int resId, final Object target) { - if (resId == 0) return null; + if (resId == 0) { + return null; + } final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator( getContext(), resId); if (animator != null) { @@ -417,20 +428,23 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack return animator; } - // Getter/setter methods for {@link ObjectAnimator}. + @ExternallyReferenced public int getLanguageOnSpacebarAnimAlpha() { return mLanguageOnSpacebarAnimAlpha; } + @ExternallyReferenced public void setLanguageOnSpacebarAnimAlpha(final int alpha) { mLanguageOnSpacebarAnimAlpha = alpha; invalidateKey(mSpaceKey); } + @ExternallyReferenced public int getAltCodeKeyWhileTypingAnimAlpha() { return mAltCodeKeyWhileTypingAnimAlpha; } + @ExternallyReferenced public void setAltCodeKeyWhileTypingAnimAlpha(final int alpha) { mAltCodeKeyWhileTypingAnimAlpha = alpha; updateAltCodeKeyWhileTyping(); @@ -480,10 +494,10 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mKeyDetector.setKeyboard( keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); PointerTracker.setKeyDetector(mKeyDetector); - mTouchScreenRegulator.setKeyboard(keyboard); + mTouchScreenRegulator.setKeyboardGeometry(keyboard.mOccupiedWidth); mMoreKeysPanelCache.clear(); - mSpaceKey = keyboard.getKey(Keyboard.CODE_SPACE); + mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); mSpaceIcon = (mSpaceKey != null) ? mSpaceKey.getIcon(keyboard.mIconsSet, Constants.Color.ALPHA_OPAQUE) : null; final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; @@ -506,18 +520,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack PointerTracker.setGestureHandlingEnabledByUser(gestureHandlingEnabledByUser); } - /** - * Returns whether the device has distinct multi-touch panel. - * @return true if the device has distinct multi-touch panel. - */ - public boolean hasDistinctMultitouch() { - return mHasDistinctMultitouch; - } - - public void setDistinctMultitouch(final boolean hasDistinctMultitouch) { - mHasDistinctMultitouch = hasDistinctMultitouch; - } - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -553,21 +555,25 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } // Check if we are already displaying popup panel. - if (mMoreKeysPanel != null) + if (mMoreKeysPanel != null) { return false; - if (parentKey == null) + } + if (parentKey == null) { return false; + } return onLongPress(parentKey, tracker); } // This default implementation returns a more keys panel. protected MoreKeysPanel onCreateMoreKeysPanel(final Key parentKey) { - if (parentKey.mMoreKeys == null) + if (parentKey.mMoreKeys == null) { return null; + } final View container = LayoutInflater.from(getContext()).inflate(mMoreKeysLayout, null); - if (container == null) + if (container == null) { throw new NullPointerException(); + } final MoreKeysKeyboardView moreKeysKeyboardView = (MoreKeysKeyboardView)container.findViewById(R.id.more_keys_keyboard_view); @@ -600,7 +606,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack KeyboardSwitcher.getInstance().hapticAndAudioFeedback(primaryCode); return true; } - if (primaryCode == Keyboard.CODE_SPACE || primaryCode == Keyboard.CODE_LANGUAGE_SWITCH) { + if (primaryCode == Constants.CODE_SPACE || primaryCode == Constants.CODE_LANGUAGE_SWITCH) { // Long pressing the space key invokes IME switcher dialog. if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) { tracker.onLongPressed(); @@ -628,8 +634,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack MoreKeysPanel moreKeysPanel = mMoreKeysPanelCache.get(parentKey); if (moreKeysPanel == null) { moreKeysPanel = onCreateMoreKeysPanel(parentKey); - if (moreKeysPanel == null) + if (moreKeysPanel == null) { return false; + } mMoreKeysPanelCache.put(parentKey, moreKeysPanel); } if (mMoreKeysWindow == null) { @@ -665,9 +672,8 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public boolean isInSlidingKeyInput() { if (mMoreKeysPanel != null) { return true; - } else { - return PointerTracker.isAnyInSlidingKeyInput(); } + return PointerTracker.isAnyInSlidingKeyInput(); } public int getPointerCount() { @@ -716,39 +722,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack x = (int)me.getX(index); y = (int)me.getY(index); } - if (ENABLE_USABILITY_STUDY_LOG) { - final String eventTag; - switch (action) { - case MotionEvent.ACTION_UP: - eventTag = "[Up]"; - break; - case MotionEvent.ACTION_DOWN: - eventTag = "[Down]"; - break; - case MotionEvent.ACTION_POINTER_UP: - eventTag = "[PointerUp]"; - break; - case MotionEvent.ACTION_POINTER_DOWN: - eventTag = "[PointerDown]"; - break; - case MotionEvent.ACTION_MOVE: // Skip this as being logged below - eventTag = ""; - break; - default: - eventTag = "[Action" + action + "]"; - break; - } - if (!TextUtils.isEmpty(eventTag)) { - final float size = me.getSize(index); - final float pressure = me.getPressure(index); - UsabilityStudyLogUtils.getInstance().write( - eventTag + eventTime + "," + id + "," + x + "," + y + "," - + size + "," + pressure); - } + // TODO: This might be moved to the tracker.processMotionEvent() call below. + if (ENABLE_USABILITY_STUDY_LOG && action != MotionEvent.ACTION_MOVE) { + writeUsabilityStudyLog(me, action, eventTime, index, id, x, y); } + // TODO: This should be moved to the tracker.processMotionEvent() call below. + // Currently the same "move" event is being logged twice. if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, index, id, - x, y); + ResearchLogger.mainKeyboardView_processMotionEvent( + me, action, eventTime, index, id, x, y); } if (mKeyTimerHandler.isInKeyRepeat()) { @@ -774,8 +756,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack final Key newKey = tracker.getKeyOn(x, y); if (mOldKey != newKey) { tracker.onDownEvent(x, y, eventTime, this); - if (action == MotionEvent.ACTION_UP) + if (action == MotionEvent.ACTION_UP) { tracker.onUpEvent(x, y, eventTime); + } } } else if (pointerCount == 2 && oldPointerCount == 1) { // Single-touch to multi-touch transition. @@ -812,15 +795,11 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } tracker.onMoveEvent(px, py, eventTime, motionEvent); if (ENABLE_USABILITY_STUDY_LOG) { - final float pointerSize = me.getSize(i); - final float pointerPressure = me.getPressure(i); - UsabilityStudyLogUtils.getInstance().write("[Move]" + eventTime + "," - + pointerId + "," + px + "," + py + "," - + pointerSize + "," + pointerPressure); + writeUsabilityStudyLog(me, action, eventTime, i, pointerId, px, py); } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.mainKeyboardView_processMotionEvent(me, action, eventTime, - i, pointerId, px, py); + ResearchLogger.mainKeyboardView_processMotionEvent( + me, action, eventTime, i, pointerId, px, py); } } } else { @@ -831,6 +810,35 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack return true; } + private static void writeUsabilityStudyLog(final MotionEvent me, final int action, + final long eventTime, final int index, final int id, final int x, final int y) { + final String eventTag; + switch (action) { + case MotionEvent.ACTION_UP: + eventTag = "[Up]"; + break; + case MotionEvent.ACTION_DOWN: + eventTag = "[Down]"; + break; + case MotionEvent.ACTION_POINTER_UP: + eventTag = "[PointerUp]"; + break; + case MotionEvent.ACTION_POINTER_DOWN: + eventTag = "[PointerDown]"; + break; + case MotionEvent.ACTION_MOVE: + eventTag = "[Move]"; + break; + default: + eventTag = "[Action" + action + "]"; + break; + } + final float size = me.getSize(index); + final float pressure = me.getPressure(index); + UsabilityStudyLogUtils.getInstance().write( + eventTag + eventTime + "," + id + "," + x + "," + y + "," + size + "," + pressure); + } + @Override public void closing() { super.closing(); @@ -840,14 +848,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack @Override public boolean dismissMoreKeysPanel() { - if (mMoreKeysWindow != null && mMoreKeysWindow.isShowing()) { - mMoreKeysWindow.dismiss(); - mMoreKeysPanel = null; - mMoreKeysPanelPointerTrackerId = -1; - dimEntireKeyboard(false); - return true; + if (mMoreKeysWindow == null || !mMoreKeysWindow.isShowing()) { + return false; } - return false; + mMoreKeysWindow.dismiss(); + mMoreKeysPanel = null; + mMoreKeysPanelPointerTrackerId = -1; + dimEntireKeyboard(false); + return true; } /** @@ -870,16 +878,22 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack public void updateShortcutKey(final boolean available) { final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; - final Key shortcutKey = keyboard.getKey(Keyboard.CODE_SHORTCUT); - if (shortcutKey == null) return; + if (keyboard == null) { + return; + } + final Key shortcutKey = keyboard.getKey(Constants.CODE_SHORTCUT); + if (shortcutKey == null) { + return; + } shortcutKey.setEnabled(available); invalidateKey(shortcutKey); } private void updateAltCodeKeyWhileTyping() { final Keyboard keyboard = getKeyboard(); - if (keyboard == null) return; + if (keyboard == null) { + return; + } for (final Key key : keyboard.mAltCodeKeysWhileTyping) { invalidateKey(key); } @@ -909,7 +923,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } public void updateAutoCorrectionState(final boolean isAutoCorrection) { - if (!mAutoCorrectionSpacebarLedEnabled) return; + if (!mAutoCorrectionSpacebarLedEnabled) { + return; + } mAutoCorrectionSpacebarLedOn = isAutoCorrection; invalidateKey(mSpaceKey); } @@ -920,13 +936,13 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack if (key.altCodeWhileTyping() && key.isEnabled()) { params.mAnimAlpha = mAltCodeKeyWhileTypingAnimAlpha; } - if (key.mCode == Keyboard.CODE_SPACE) { + if (key.mCode == Constants.CODE_SPACE) { drawSpacebar(key, canvas, paint); // Whether space key needs to show the "..." popup hint for special purposes if (key.isLongPressEnabled() && mHasMultipleEnabledIMEsOrSubtypes) { drawKeyPopupHint(key, canvas, paint, params); } - } else if (key.mCode == Keyboard.CODE_LANGUAGE_SWITCH) { + } else if (key.mCode == Constants.CODE_LANGUAGE_SWITCH) { super.onDrawKeyTopVisuals(key, canvas, paint, params); drawKeyPopupHint(key, canvas, paint, params); } else { @@ -937,10 +953,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private boolean fitsTextIntoWidth(final int width, final String text, final Paint paint) { paint.setTextScaleX(1.0f); final float textWidth = getLabelWidth(text, paint); - if (textWidth < width) return true; + if (textWidth < width) { + return true; + } final float scaleX = width / textWidth; - if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) return false; + if (scaleX < MINIMUM_XSCALE_OF_LANGUAGE_NAME) { + return false; + } paint.setTextScaleX(scaleX); return getLabelWidth(text, paint) < width; @@ -950,19 +970,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private String layoutLanguageOnSpacebar(final Paint paint, final InputMethodSubtype subtype, final int width) { // Choose appropriate language name to fit into the width. - String text = getFullDisplayName(subtype, getResources()); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String fullText = getFullDisplayName(subtype, getResources()); + if (fitsTextIntoWidth(width, fullText, paint)) { + return fullText; } - text = getMiddleDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String middleText = getMiddleDisplayName(subtype); + if (fitsTextIntoWidth(width, middleText, paint)) { + return middleText; } - text = getShortDisplayName(subtype); - if (fitsTextIntoWidth(width, text, paint)) { - return text; + final String shortText = getShortDisplayName(subtype); + if (fitsTextIntoWidth(width, shortText, paint)) { + return shortText; } return ""; @@ -1009,18 +1029,19 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack // InputMethodSubtype's display name for spacebar text in its locale. // isAdditionalSubtype (T=true, F=false) - // locale layout | Short Middle Full - // ------ ------ - ---- --------- ---------------------- - // en_US qwerty F En English English (US) exception - // en_GB qwerty F En English English (UK) exception - // fr azerty F Fr Français Français - // fr_CA qwerty F Fr Français Français (Canada) - // de qwertz F De Deutsch Deutsch - // zz qwerty F QWERTY QWERTY - // fr qwertz T Fr Français Français (QWERTZ) - // de qwerty T De Deutsch Deutsch (QWERTY) - // en_US azerty T En English English (US) (AZERTY) - // zz azerty T AZERTY AZERTY + // locale layout | Short Middle Full + // ------ ------- - ---- --------- ---------------------- + // en_US qwerty F En English English (US) exception + // en_GB qwerty F En English English (UK) exception + // es_US spanish F Es Español Español (EE.UU.) exception + // fr azerty F Fr Français Français + // fr_CA qwerty F Fr Français Français (Canada) + // de qwertz F De Deutsch Deutsch + // zz qwerty F QWERTY QWERTY + // fr qwertz T Fr Français Français (QWERTZ) + // de qwerty T De Deutsch Deutsch (QWERTY) + // en_US azerty T En English English (US) (AZERTY) + // zz azerty T AZERTY AZERTY // Get InputMethodSubtype's full display name in its locale. static String getFullDisplayName(final InputMethodSubtype subtype, final Resources res) { diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java index d7d4be40b..3826a39a4 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java @@ -20,6 +20,7 @@ import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.view.View; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -39,7 +40,7 @@ public final class MoreKeysKeyboard extends Keyboard { return mDefaultKeyCoordX; } - /* package for test */ + @UsedForTesting static class MoreKeysKeyboardParams extends KeyboardParams { public boolean mIsFixedOrder; /* package */int mTopRowAdjustment; diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index a50617693..9c450e994 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -48,7 +48,7 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys private final KeyboardActionListener mMoreKeysKeyboardListener = new KeyboardActionListener.Adapter() { @Override - public void onCodeInput(int primaryCode, int x, int y) { + public void onCodeInput(final int primaryCode, final int x, final int y) { // Because a more keys keyboard doesn't need proximity characters correction, we don't // send touch event coordinates. mListener.onCodeInput( @@ -56,7 +56,7 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void onTextInput(CharSequence text) { + public void onTextInput(final String text) { mListener.onTextInput(text); } @@ -66,12 +66,12 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void onUpdateBatchInput(InputPointers batchPointers) { + public void onUpdateBatchInput(final InputPointers batchPointers) { mListener.onUpdateBatchInput(batchPointers); } @Override - public void onEndBatchInput(InputPointers batchPointers) { + public void onEndBatchInput(final InputPointers batchPointers) { mListener.onEndBatchInput(batchPointers); } @@ -81,21 +81,22 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void onPressKey(int primaryCode) { + public void onPressKey(final int primaryCode) { mListener.onPressKey(primaryCode); } @Override - public void onReleaseKey(int primaryCode, boolean withSliding) { + public void onReleaseKey(final int primaryCode, final boolean withSliding) { mListener.onReleaseKey(primaryCode, withSliding); } }; - public MoreKeysKeyboardView(Context context, AttributeSet attrs) { + public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } - public MoreKeysKeyboardView(Context context, AttributeSet attrs, int defStyle) { + public MoreKeysKeyboardView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); final Resources res = context.getResources(); @@ -105,7 +106,7 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); @@ -117,7 +118,7 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void setKeyboard(Keyboard keyboard) { + public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop() + mVerticalCorrection); @@ -144,15 +145,16 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { + public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { // More keys keyboard needs no pop-up key preview displayed, so we pass always false with a // delay of 0. The delay does not matter actually since the popup is not shown anyway. super.setKeyPreviewPopupEnabled(false, 0); } @Override - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener) { + public void showMoreKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final PopupWindow window, + final KeyboardActionListener listener) { mController = controller; mListener = listener; final View container = (View)getParent(); @@ -185,12 +187,12 @@ public final class MoreKeysKeyboardView extends KeyboardView implements MoreKeys } @Override - public int translateX(int x) { + public int translateX(final int x) { return x - mOriginX; } @Override - public int translateY(int y) { + public int translateY(final int y) { return y - mOriginY; } } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index d0f7cb276..c86805232 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -27,6 +27,7 @@ import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokePara import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -156,7 +157,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private static final boolean sNeedsProximateBogusDownMoveUpEventHack = true; private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); - private static PointerTrackerQueue sPointerTrackerQueue; + private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue(); public final int mPointerId; @@ -325,13 +326,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { private final GestureStrokeWithPreviewPoints mGestureStrokeWithPreviewPoints; - public static void init(boolean hasDistinctMultitouch, - boolean needsPhantomSuddenMoveEventHack) { - if (hasDistinctMultitouch) { - sPointerTrackerQueue = new PointerTrackerQueue(); - } else { - sPointerTrackerQueue = null; - } + public static void init(final boolean needsPhantomSuddenMoveEventHack) { sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; sParams = PointerTrackerParams.DEFAULT; sGestureStrokeParams = GestureStrokeParams.DEFAULT; @@ -375,7 +370,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } public static boolean isAnyInSlidingKeyInput() { - return sPointerTrackerQueue != null ? sPointerTrackerQueue.isAnyInSlidingKeyInput() : false; + return sPointerTrackerQueue.isAnyInSlidingKeyInput(); } public static void setKeyboardActionListener(final KeyboardActionListener listener) { @@ -453,8 +448,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean altersCode = key.altCodeWhileTyping() && mTimerProxy.isTypingState(); final int code = altersCode ? key.getAltCode() : primaryCode; if (DEBUG_LISTENER) { - final String output = code == Keyboard.CODE_OUTPUT_TEXT - ? key.getOutputText() : Keyboard.printableCode(code); + final String output = code == Constants.CODE_OUTPUT_TEXT + ? key.getOutputText() : Constants.printableCode(code); Log.d(TAG, String.format("[%d] onCodeInput: %4d %4d %s%s%s", mPointerId, x, y, output, ignoreModifierKey ? " ignoreModifier" : "", altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); @@ -469,9 +464,9 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Even if the key is disabled, it should respond if it is in the altCodeWhileTyping state. if (key.isEnabled() || altersCode) { sTimeRecorder.onCodeInput(code, eventTime); - if (code == Keyboard.CODE_OUTPUT_TEXT) { + if (code == Constants.CODE_OUTPUT_TEXT) { mListener.onTextInput(key.getOutputText()); - } else if (code != Keyboard.CODE_UNSPECIFIED) { + } else if (code != Constants.CODE_UNSPECIFIED) { mListener.onCodeInput(code, x, y); } } @@ -487,7 +482,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final boolean ignoreModifierKey = mIsInSlidingKeyInputFromModifier && key.isModifier(); if (DEBUG_LISTENER) { Log.d(TAG, String.format("[%d] onRelease : %s%s%s%s", mPointerId, - Keyboard.printableCode(primaryCode), + Constants.printableCode(primaryCode), withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", key.isEnabled() ? "": " disabled")); } @@ -682,7 +677,11 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } private static int getActivePointerTrackerCount() { - return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); + return sPointerTrackerQueue.size(); + } + + private static boolean isOldestTrackerInQueue(final PointerTracker tracker) { + return sPointerTrackerQueue.getOldestElement() == tracker; } private void mayStartBatchInput(final Key key) { @@ -702,8 +701,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sLastRecognitionTime = 0; mListener.onStartBatchInput(); } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + mTimerProxy.cancelLongPressTimer(); + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); } private void mayUpdateBatchInput(final long eventTime, final Key key) { @@ -724,8 +723,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } } } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); } private void mayEndBatchInput(final long eventTime) { @@ -741,8 +739,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { mListener.onEndBatchInput(sAggregratedPointers); } } - final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; - mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); + mDrawingProxy.showGesturePreviewTrail(this, isOldestTrackerInQueue(this)); } public void processMotionEvent(final int action, final int x, final int y, final long eventTime, @@ -794,22 +791,19 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final Key key = getKeyOn(x, y); mBogusMoveEventDetector.onActualDownEvent(x, y); - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - if (key != null && key.isModifier()) { - // Before processing a down event of modifier key, all pointers already being - // tracked should be released. - queue.releaseAllPointers(eventTime); - } - queue.add(this); + if (key != null && key.isModifier()) { + // Before processing a down event of modifier key, all pointers already being + // tracked should be released. + sPointerTrackerQueue.releaseAllPointers(eventTime); } + sPointerTrackerQueue.add(this); onDownEventInternal(x, y, eventTime); if (!sShouldHandleGesture) { return; } - // A gesture should start only from the letter key. + // A gesture should start only from a non-modifier key. mIsDetectingGesture = (mKeyboard != null) && mKeyboard.mId.isAlphabetKeyboard() - && !mIsShowingMoreKeysPanel && key != null && Keyboard.isLetterCode(key.mCode); + && !mIsShowingMoreKeysPanel && key != null && !key.isModifier(); if (mIsDetectingGesture) { if (getActivePointerTrackerCount() == 1) { sGestureFirstDownTime = eventTime; @@ -891,136 +885,150 @@ public final class PointerTracker implements PointerTrackerQueue.Element { onMoveEventInternal(x, y, eventTime); } + private void processSlidingKeyInput(final Key newKey, final int x, final int y, + final long eventTime) { + // This onPress call may have changed keyboard layout. Those cases are detected + // at {@link #setKeyboard}. In those cases, we should update key according + // to the new keyboard layout. + Key key = newKey; + if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { + key = onMoveKey(x, y); + } + onMoveToNewKey(key, x, y); + startLongPressTimer(key); + setPressedKeyGraphics(key, eventTime); + } + + private void processPhantomSuddenMoveHack(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + if (DEBUG_MODE) { + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " phantom sudden move event (distance=%d) is translated to " + + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, + getDistance(x, y, lastX, lastY), + lastX, lastY, Constants.printableCode(oldKey.mCode), + x, y, Constants.printableCode(key.mCode))); + } + // TODO: This should be moved to outside of this nested if-clause? + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); + } + onUpEventInternal(eventTime); + onDownEventInternal(x, y, eventTime); + } + + private void processProximateBogusDownMoveUpEventHack(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + if (DEBUG_MODE) { + final float keyDiagonal = (float)Math.hypot( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); + final float radiusRatio = + mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) + / keyDiagonal; + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " bogus down-move-up event (raidus=%.2f key diagonal) is " + + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", + mPointerId, radiusRatio, + lastX, lastY, Constants.printableCode(oldKey.mCode), + x, y, Constants.printableCode(key.mCode))); + } + onUpEventInternal(eventTime); + onDownEventInternal(x, y, eventTime); + } + + private void processSildeOutFromOldKey(final Key oldKey) { + setReleasedKeyGraphics(oldKey); + callListenerOnRelease(oldKey, oldKey.mCode, true); + startSlidingKeyInput(oldKey); + mTimerProxy.cancelKeyTimers(); + } + + private void slideFromOldKeyToNewKey(final Key key, final int x, final int y, + final long eventTime, final Key oldKey, final int lastX, final int lastY) { + // The pointer has been slid in to the new key from the previous key, we must call + // onRelease() first to notify that the previous key has been released, then call + // onPress() to notify that the new key is being pressed. + processSildeOutFromOldKey(oldKey); + startRepeatKey(key); + if (mIsAllowedSlidingKeyInput) { + processSlidingKeyInput(key, x, y, eventTime); + } + // HACK: On some devices, quick successive touches may be reported as a sudden move by + // touch panel firmware. This hack detects such cases and translates the move event to + // successive up and down events. + // TODO: Should find a way to balance gesture detection and this hack. + else if (sNeedsPhantomSuddenMoveEventHack + && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { + processPhantomSuddenMoveHack(key, x, y, eventTime, oldKey, lastX, lastY); + } + // HACK: On some devices, quick successive proximate touches may be reported as a bogus + // down-move-up event by touch panel firmware. This hack detects such cases and breaks + // these events into separate up and down events. + else if (sNeedsProximateBogusDownMoveUpEventHack && sTimeRecorder.isInFastTyping(eventTime) + && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { + processProximateBogusDownMoveUpEventHack(key, x, y, eventTime, oldKey, lastX, lastY); + } + // HACK: If there are currently multiple touches, register the key even if the finger + // slides off the key. This defends against noise from some touch panels when there are + // close multiple touches. + // Caveat: When in chording input mode with a modifier key, we don't use this hack. + else if (getActivePointerTrackerCount() > 1 + && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { + if (DEBUG_MODE) { + Log.w(TAG, String.format("[%d] onMoveEvent:" + + " detected sliding finger while multi touching", mPointerId)); + } + onUpEvent(x, y, eventTime); + mKeyAlreadyProcessed = true; + setReleasedKeyGraphics(oldKey); + } else { + if (!mIsDetectingGesture) { + mKeyAlreadyProcessed = true; + } + setReleasedKeyGraphics(oldKey); + } + } + + private void slideOutFromOldKey(final Key oldKey, final int x, final int y) { + // The pointer has been slid out from the previous key, we must call onRelease() to + // notify that the previous key has been released. + processSildeOutFromOldKey(oldKey); + if (mIsAllowedSlidingKeyInput) { + onMoveToNewKey(null, x, y); + } else { + if (!mIsDetectingGesture) { + mKeyAlreadyProcessed = true; + } + } + } + private void onMoveEventInternal(final int x, final int y, final long eventTime) { final int lastX = mLastX; final int lastY = mLastY; final Key oldKey = mCurrentKey; - Key key = onMoveKey(x, y); + final Key newKey = onMoveKey(x, y); if (sShouldHandleGesture) { // Register move event on gesture tracker. - onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, key); + onGestureMoveEvent(x, y, eventTime, true /* isMajorEvent */, newKey); if (sInGesture) { - mTimerProxy.cancelLongPressTimer(); mCurrentKey = null; setReleasedKeyGraphics(oldKey); return; } } - if (key != null) { - if (oldKey == null) { + if (newKey != null) { + if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { + slideFromOldKeyToNewKey(newKey, x, y, eventTime, oldKey, lastX, lastY); + } else if (oldKey == null) { // The pointer has been slid in to the new key, but the finger was not on any keys. // In this case, we must call onPress() to notify that the new key is being pressed. - // This onPress call may have changed keyboard layout. Those cases are detected at - // {@link #setKeyboard}. In those cases, we should update key according to the - // new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { - key = onMoveKey(x, y); - } - onMoveToNewKey(key, x, y); - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } else if (isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { - // The pointer has been slid in to the new key from the previous key, we must call - // onRelease() first to notify that the previous key has been released, then call - // onPress() to notify that the new key is being pressed. - setReleasedKeyGraphics(oldKey); - callListenerOnRelease(oldKey, oldKey.mCode, true); - startSlidingKeyInput(oldKey); - mTimerProxy.cancelKeyTimers(); - startRepeatKey(key); - if (mIsAllowedSlidingKeyInput) { - // This onPress call may have changed keyboard layout. Those cases are detected - // at {@link #setKeyboard}. In those cases, we should update key according - // to the new keyboard layout. - if (callListenerOnPressAndCheckKeyboardLayoutChange(key)) { - key = onMoveKey(x, y); - } - onMoveToNewKey(key, x, y); - startLongPressTimer(key); - setPressedKeyGraphics(key, eventTime); - } else { - // HACK: On some devices, quick successive touches may be reported as a sudden - // move by touch panel firmware. This hack detects such cases and translates the - // move event to successive up and down events. - // TODO: Should find a way to balance gesture detection and this hack. - if (sNeedsPhantomSuddenMoveEventHack - && getDistance(x, y, lastX, lastY) >= mPhantonSuddenMoveThreshold) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " phantom sudden move event (distance=%d) is translated to " - + "up[%d,%d,%s]/down[%d,%d,%s] events", mPointerId, - getDistance(x, y, lastX, lastY), - lastX, lastY, Keyboard.printableCode(oldKey.mCode), - x, y, Keyboard.printableCode(key.mCode))); - } - // TODO: This should be moved to outside of this nested if-clause? - if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); - } - onUpEventInternal(eventTime); - onDownEventInternal(x, y, eventTime); - } - // HACK: On some devices, quick successive proximate touches may be reported as - // a bogus down-move-up event by touch panel firmware. This hack detects such - // cases and breaks these events into separate up and down events. - else if (sNeedsProximateBogusDownMoveUpEventHack - && sTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.isCloseToActualDownEvent(x, y)) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float radiusRatio = - mBogusMoveEventDetector.getDistanceFromDownEvent(x, y) - / keyDiagonal; - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " bogus down-move-up event (raidus=%.2f key diagonal) is " - + " translated to up[%d,%d,%s]/down[%d,%d,%s] events", - mPointerId, radiusRatio, - lastX, lastY, Keyboard.printableCode(oldKey.mCode), - x, y, Keyboard.printableCode(key.mCode))); - } - onUpEventInternal(eventTime); - onDownEventInternal(x, y, eventTime); - } else { - // HACK: If there are currently multiple touches, register the key even if - // the finger slides off the key. This defends against noise from some - // touch panels when there are close multiple touches. - // Caveat: When in chording input mode with a modifier key, we don't use - // this hack. - if (getActivePointerTrackerCount() > 1 && sPointerTrackerQueue != null - && !sPointerTrackerQueue.hasModifierKeyOlderThan(this)) { - if (DEBUG_MODE) { - Log.w(TAG, String.format("[%d] onMoveEvent:" - + " detected sliding finger while multi touching", - mPointerId)); - } - onUpEvent(x, y, eventTime); - mKeyAlreadyProcessed = true; - } - if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; - } - setReleasedKeyGraphics(oldKey); - } - } + processSlidingKeyInput(newKey, x, y, eventTime); } - } else { - if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, key)) { - // The pointer has been slid out from the previous key, we must call onRelease() to - // notify that the previous key has been released. - setReleasedKeyGraphics(oldKey); - callListenerOnRelease(oldKey, oldKey.mCode, true); - startSlidingKeyInput(oldKey); - mTimerProxy.cancelLongPressTimer(); - if (mIsAllowedSlidingKeyInput) { - onMoveToNewKey(key, x, y); - } else { - if (!mIsDetectingGesture) { - mKeyAlreadyProcessed = true; - } - } + } else { // newKey == null + if (oldKey != null && isMajorEnoughMoveToBeOnNewKey(x, y, eventTime, newKey)) { + slideOutFromOldKey(oldKey, x, y); } } } @@ -1030,22 +1038,17 @@ public final class PointerTracker implements PointerTrackerQueue.Element { printTouchEvent("onUpEvent :", x, y, eventTime); } - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - if (!sInGesture) { - if (mCurrentKey != null && mCurrentKey.isModifier()) { - // Before processing an up event of modifier key, all pointers already being - // tracked should be released. - queue.releaseAllPointersExcept(this, eventTime); - } else { - queue.releaseAllPointersOlderThan(this, eventTime); - } + if (!sInGesture) { + if (mCurrentKey != null && mCurrentKey.isModifier()) { + // Before processing an up event of modifier key, all pointers already being + // tracked should be released. + sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); + } else { + sPointerTrackerQueue.releaseAllPointersOlderThan(this, eventTime); } } onUpEventInternal(eventTime); - if (queue != null) { - queue.remove(this); - } + sPointerTrackerQueue.remove(this); } // Let this pointer tracker know that one of newer-than-this pointer trackers got an up event. @@ -1098,10 +1101,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { public void onLongPressed() { mKeyAlreadyProcessed = true; setReleasedKeyGraphics(mCurrentKey); - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - queue.remove(this); - } + sPointerTrackerQueue.remove(this); } public void onCancelEvent(final int x, final int y, final long eventTime) { @@ -1109,11 +1109,8 @@ public final class PointerTracker implements PointerTrackerQueue.Element { printTouchEvent("onCancelEvt:", x, y, eventTime); } - final PointerTrackerQueue queue = sPointerTrackerQueue; - if (queue != null) { - queue.releaseAllPointersExcept(this, eventTime); - queue.remove(this); - } + sPointerTrackerQueue.releaseAllPointersExcept(this, eventTime); + sPointerTrackerQueue.remove(this); onCancelEventInternal(); } @@ -1149,38 +1146,38 @@ public final class PointerTracker implements PointerTrackerQueue.Element { final Key curKey = mCurrentKey; if (newKey == curKey) { return false; - } else if (curKey != null) { - final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( - mIsInSlidingKeyInputFromModifier); - final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); - if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { - if (DEBUG_MODE) { - final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) - / mKeyboard.mMostCommonKeyWidth; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - +" %.2f key width from key edge", - mPointerId, distanceToEdgeRatio)); - } - return true; + } + if (curKey == null /* && newKey != null */) { + return true; + } + // Here curKey points to the different key from newKey. + final int keyHysteresisDistanceSquared = mKeyDetector.getKeyHysteresisDistanceSquared( + mIsInSlidingKeyInputFromModifier); + final int distanceFromKeyEdgeSquared = curKey.squaredDistanceToEdge(x, y); + if (distanceFromKeyEdgeSquared >= keyHysteresisDistanceSquared) { + if (DEBUG_MODE) { + final float distanceToEdgeRatio = (float)Math.sqrt(distanceFromKeyEdgeSquared) + / mKeyboard.mMostCommonKeyWidth; + Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" + +" %.2f key width from key edge", mPointerId, distanceToEdgeRatio)); } - if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput - && sTimeRecorder.isInFastTyping(eventTime) - && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { - if (DEBUG_MODE) { - final float keyDiagonal = (float)Math.hypot( - mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); - final float lengthFromDownRatio = - mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; - Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" - + " %.2f key diagonal from virtual down point", - mPointerId, lengthFromDownRatio)); - } - return true; + return true; + } + if (sNeedsProximateBogusDownMoveUpEventHack && !mIsAllowedSlidingKeyInput + && sTimeRecorder.isInFastTyping(eventTime) + && mBogusMoveEventDetector.hasTraveledLongDistance(x, y)) { + if (DEBUG_MODE) { + final float keyDiagonal = (float)Math.hypot( + mKeyboard.mMostCommonKeyWidth, mKeyboard.mMostCommonKeyHeight); + final float lengthFromDownRatio = + mBogusMoveEventDetector.mAccumulatedDistanceFromDownKey / keyDiagonal; + Log.d(TAG, String.format("[%d] isMajorEnoughMoveToBeOnNewKey:" + + " %.2f key diagonal from virtual down point", + mPointerId, lengthFromDownRatio)); } - return false; - } else { // curKey == null && newKey != null return true; } + return false; } private void startLongPressTimer(final Key key) { diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 06a9e9252..ed01f3458 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard; import android.graphics.Rect; import android.text.TextUtils; +import android.util.Log; import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; import com.android.inputmethod.latin.Constants; @@ -26,11 +27,14 @@ import com.android.inputmethod.latin.JniUtils; import java.util.Arrays; public final class ProximityInfo { + private static final String TAG = ProximityInfo.class.getSimpleName(); + private static final boolean DEBUG = false; + /** MAX_PROXIMITY_CHARS_SIZE must be the same as MAX_PROXIMITY_CHARS_SIZE_INTERNAL * in defines.h */ public static final int MAX_PROXIMITY_CHARS_SIZE = 16; /** Number of key widths from current touch point to search for nearest keys. */ - private static float SEARCH_DISTANCE = 1.2f; + private static final float SEARCH_DISTANCE = 1.2f; private static final Key[] EMPTY_KEY_ARRAY = new Key[0]; private static final float DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS = 0.15f; @@ -140,9 +144,13 @@ public final class ProximityInfo { } if (touchPositionCorrection != null && touchPositionCorrection.isValid()) { + if (DEBUG) { + Log.d(TAG, "touchPositionCorrection: ON"); + } sweetSpotCenterXs = new float[keyCount]; sweetSpotCenterYs = new float[keyCount]; sweetSpotRadii = new float[keyCount]; + final int rows = touchPositionCorrection.getRows(); final float defaultRadius = DEFAULT_TOUCH_POSITION_CORRECTION_RADIUS * (float)Math.hypot(mMostCommonKeyWidth, mMostCommonKeyHeight); for (int i = 0; i < keyCount; i++) { @@ -152,7 +160,7 @@ public final class ProximityInfo { sweetSpotCenterYs[i] = hitBox.exactCenterY(); sweetSpotRadii[i] = defaultRadius; final int row = hitBox.top / mMostCommonKeyHeight; - if (row < touchPositionCorrection.getRows()) { + if (row < rows) { final int hitBoxWidth = hitBox.width(); final int hitBoxHeight = hitBox.height(); final float hitBoxDiagonal = (float)Math.hypot(hitBoxWidth, hitBoxHeight); @@ -160,9 +168,19 @@ public final class ProximityInfo { sweetSpotCenterYs[i] += touchPositionCorrection.getY(row) * hitBoxHeight; sweetSpotRadii[i] = touchPositionCorrection.getRadius(row) * hitBoxDiagonal; } + if (DEBUG) { + Log.d(TAG, String.format( + " [%2d] row=%d x/y/r=%7.2f/%7.2f/%5.2f %s code=%s", i, row, + sweetSpotCenterXs[i], sweetSpotCenterYs[i], sweetSpotRadii[i], + (row < rows ? "correct" : "default"), + Constants.printableCode(key.mCode))); + } } } else { sweetSpotCenterXs = sweetSpotCenterYs = sweetSpotRadii = null; + if (DEBUG) { + Log.d(TAG, "touchPositionCorrection: OFF"); + } } return setProximityInfoNative(mLocaleStr, MAX_PROXIMITY_CHARS_SIZE, @@ -221,7 +239,7 @@ public final class ProximityInfo { return; } int index = 0; - if (primaryKeyCode > Keyboard.CODE_SPACE) { + if (primaryKeyCode > Constants.CODE_SPACE) { dest[index++] = primaryKeyCode; } final Key[] nearestKeys = getNearestKeys(x, y); @@ -230,7 +248,7 @@ public final class ProximityInfo { break; } final int code = key.mCode; - if (code <= Keyboard.CODE_SPACE) { + if (code <= Constants.CODE_SPACE) { break; } dest[index++] = code; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 2caa5eb02..9f6e2f37e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -16,12 +16,13 @@ package com.android.inputmethod.keyboard.internal; -import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.StringUtils; @@ -172,7 +173,7 @@ public final class KeySpecParser { if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) { throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec); } - return parseCode(moreKeySpec.substring(end + 1), codesSet, Keyboard.CODE_UNSPECIFIED); + return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED); } final String outputText = getOutputTextInternal(moreKeySpec); if (outputText != null) { @@ -181,14 +182,14 @@ public final class KeySpecParser { if (StringUtils.codePointCount(outputText) == 1) { return outputText.codePointAt(0); } - return Keyboard.CODE_OUTPUT_TEXT; + return CODE_OUTPUT_TEXT; } final String label = getLabel(moreKeySpec); // Code is automatically generated for one letter label. if (StringUtils.codePointCount(label) == 1) { return label.codePointAt(0); } - return Keyboard.CODE_OUTPUT_TEXT; + return CODE_OUTPUT_TEXT; } public static int parseCode(final String text, final KeyboardCodesSet codesSet, @@ -468,7 +469,7 @@ public final class KeySpecParser { public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase, final Locale locale) { - if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code; + if (!Constants.isLetterCode(code) || !needsToUpperCase) return code; final String text = new String(new int[] { code } , 0, 1); final String casedText = KeySpecParser.toUpperCaseOfStringForLocale( text, needsToUpperCase, locale); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index b314a3795..36342688e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -27,6 +27,7 @@ import android.util.TypedValue; import android.util.Xml; import android.view.InflateException; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; @@ -177,7 +178,7 @@ public class KeyboardBuilder<KP extends KeyboardParams> { return this; } - // For test only + @UsedForTesting public void disableTouchPositionCorrectionDataForTest() { mParams.mTouchPositionCorrection.setEnabled(false); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java index 840d7133d..428e31ccd 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -16,8 +16,8 @@ package com.android.inputmethod.keyboard.internal; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import java.util.HashMap; @@ -74,21 +74,21 @@ public final class KeyboardCodesSet { private static final int CODE_RIGHT_CURLY_BRACKET = '}'; private static final int[] DEFAULT = { - Keyboard.CODE_TAB, - Keyboard.CODE_ENTER, - Keyboard.CODE_SPACE, - Keyboard.CODE_SHIFT, - Keyboard.CODE_SWITCH_ALPHA_SYMBOL, - Keyboard.CODE_OUTPUT_TEXT, - Keyboard.CODE_DELETE, - Keyboard.CODE_SETTINGS, - Keyboard.CODE_SHORTCUT, - Keyboard.CODE_ACTION_ENTER, - Keyboard.CODE_ACTION_NEXT, - Keyboard.CODE_ACTION_PREVIOUS, - Keyboard.CODE_LANGUAGE_SWITCH, - Keyboard.CODE_RESEARCH, - Keyboard.CODE_UNSPECIFIED, + Constants.CODE_TAB, + Constants.CODE_ENTER, + Constants.CODE_SPACE, + Constants.CODE_SHIFT, + Constants.CODE_SWITCH_ALPHA_SYMBOL, + Constants.CODE_OUTPUT_TEXT, + Constants.CODE_DELETE, + Constants.CODE_SETTINGS, + Constants.CODE_SHORTCUT, + Constants.CODE_ACTION_ENTER, + Constants.CODE_ACTION_NEXT, + Constants.CODE_ACTION_PREVIOUS, + Constants.CODE_LANGUAGE_SWITCH, + Constants.CODE_RESEARCH, + Constants.CODE_UNSPECIFIED, CODE_LEFT_PARENTHESIS, CODE_RIGHT_PARENTHESIS, CODE_LESS_THAN_SIGN, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java index e6fe50e02..642e1a18c 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -19,9 +19,9 @@ package com.android.inputmethod.keyboard.internal; import android.util.SparseIntArray; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import java.util.ArrayList; import java.util.TreeSet; @@ -89,7 +89,7 @@ public class KeyboardParams { mKeys.add(key); updateHistogram(key); } - if (key.mCode == Keyboard.CODE_SHIFT) { + if (key.mCode == Constants.CODE_SHIFT) { mShiftKeys.add(key); } if (key.altCodeWhileTyping()) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index 5e111fb9a..804a34b81 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -19,7 +19,6 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Constants; /** @@ -316,12 +315,12 @@ public final class KeyboardState { public void onPressKey(int code, boolean isSinglePointer, int autoCaps) { if (DEBUG_EVENT) { - Log.d(TAG, "onPressKey: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onPressKey: code=" + Constants.printableCode(code) + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { onPressShift(); - } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { onPressSymbol(); } else { mSwitchActions.cancelDoubleTapTimer(); @@ -349,12 +348,12 @@ public final class KeyboardState { public void onReleaseKey(int code, boolean withSliding) { if (DEBUG_EVENT) { - Log.d(TAG, "onReleaseKey: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onReleaseKey: code=" + Constants.printableCode(code) + " sliding=" + withSliding + " " + this); } - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { onReleaseShift(withSliding); - } else if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { onReleaseSymbol(withSliding); } } @@ -381,9 +380,9 @@ public final class KeyboardState { public void onLongPressTimeout(int code) { if (DEBUG_EVENT) { - Log.d(TAG, "onLongPressTimeout: code=" + Keyboard.printableCode(code) + " " + this); + Log.d(TAG, "onLongPressTimeout: code=" + Constants.printableCode(code) + " " + this); } - if (mIsAlphabetMode && code == Keyboard.CODE_SHIFT) { + if (mIsAlphabetMode && code == Constants.CODE_SHIFT) { mLongPressShiftLockFired = true; mSwitchActions.hapticAndAudioFeedback(code); } @@ -459,7 +458,7 @@ public final class KeyboardState { setShifted(MANUAL_SHIFT); mShiftKeyState.onPress(); } - mSwitchActions.startLongPressTimer(Keyboard.CODE_SHIFT); + mSwitchActions.startLongPressTimer(Constants.CODE_SHIFT); } } else { // In symbol mode, just toggle symbol and symbol more keyboard. @@ -539,7 +538,7 @@ public final class KeyboardState { } private static boolean isSpaceCharacter(int c) { - return c == Keyboard.CODE_SPACE || c == Keyboard.CODE_ENTER; + return c == Constants.CODE_SPACE || c == Constants.CODE_ENTER; } private boolean isLayoutSwitchBackCharacter(int c) { @@ -550,14 +549,14 @@ public final class KeyboardState { public void onCodeInput(int code, boolean isSinglePointer, int autoCaps) { if (DEBUG_EVENT) { - Log.d(TAG, "onCodeInput: code=" + Keyboard.printableCode(code) + Log.d(TAG, "onCodeInput: code=" + Constants.printableCode(code) + " single=" + isSinglePointer + " autoCaps=" + autoCaps + " " + this); } switch (mSwitchState) { case SWITCH_STATE_MOMENTARY_ALPHA_AND_SYMBOL: - if (code == Keyboard.CODE_SWITCH_ALPHA_SYMBOL) { + if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) { // Detected only the mode change key has been pressed, and then released. if (mIsAlphabetMode) { mSwitchState = SWITCH_STATE_ALPHA; @@ -573,7 +572,7 @@ public final class KeyboardState { } break; case SWITCH_STATE_MOMENTARY_SYMBOL_AND_MORE: - if (code == Keyboard.CODE_SHIFT) { + if (code == Constants.CODE_SHIFT) { // Detected only the shift key has been pressed on symbol layout, and then released. mSwitchState = SWITCH_STATE_SYMBOL_BEGIN; } else if (isSinglePointer) { @@ -584,8 +583,8 @@ public final class KeyboardState { } break; case SWITCH_STATE_SYMBOL_BEGIN: - if (!isSpaceCharacter(code) && (Keyboard.isLetterCode(code) - || code == Keyboard.CODE_OUTPUT_TEXT)) { + if (!isSpaceCharacter(code) && (Constants.isLetterCode(code) + || code == Constants.CODE_OUTPUT_TEXT)) { mSwitchState = SWITCH_STATE_SYMBOL; } // Switch back to alpha keyboard mode immediately if user types one of the switch back @@ -606,7 +605,7 @@ public final class KeyboardState { } // If the code is a letter, update keyboard shift state. - if (Keyboard.isLetterCode(code)) { + if (Constants.isLetterCode(code)) { updateAlphabetShiftState(autoCaps); } } @@ -634,7 +633,7 @@ public final class KeyboardState { @Override public String toString() { return "[keyboard=" + (mIsAlphabetMode ? mAlphabetShiftState.toString() - : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + : (mIsSymbolShifted ? "SYMBOLS_SHIFTED" : "SYMBOLS")) + " shift=" + mShiftKeyState + " symbol=" + mSymbolKeyState + " switch=" + switchStateToString(mSwitchState) + "]"; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 3bb272f8c..9d0564315 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.Context; import android.content.res.Resources; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; @@ -64,7 +65,7 @@ public final class KeyboardTextsSet { loadStringResourcesInternal(context, RESOURCE_NAMES, R.string.english_ime_name); } - /* package for test */ + @UsedForTesting void loadStringResourcesInternal(Context context, final String[] resourceNames, int referenceId) { final Resources res = context.getResources(); @@ -150,89 +151,88 @@ public final class KeyboardTextsSet { /* 43 */ "more_keys_for_double_quote", /* 44 */ "more_keys_for_tablet_double_quote", /* 45 */ "more_keys_for_currency_dollar", - /* 46 */ "more_keys_for_currency_euro", - /* 47 */ "more_keys_for_currency_pound", - /* 48 */ "more_keys_for_currency_general", - /* 49 */ "more_keys_for_punctuation", - /* 50 */ "more_keys_for_star", - /* 51 */ "more_keys_for_bullet", - /* 52 */ "more_keys_for_plus", - /* 53 */ "more_keys_for_left_parenthesis", - /* 54 */ "more_keys_for_right_parenthesis", - /* 55 */ "more_keys_for_less_than", - /* 56 */ "more_keys_for_greater_than", - /* 57 */ "more_keys_for_arabic_diacritics", - /* 58 */ "keyhintlabel_for_arabic_diacritics", - /* 59 */ "keylabel_for_symbols_1", - /* 60 */ "keylabel_for_symbols_2", - /* 61 */ "keylabel_for_symbols_3", - /* 62 */ "keylabel_for_symbols_4", - /* 63 */ "keylabel_for_symbols_5", - /* 64 */ "keylabel_for_symbols_6", - /* 65 */ "keylabel_for_symbols_7", - /* 66 */ "keylabel_for_symbols_8", - /* 67 */ "keylabel_for_symbols_9", - /* 68 */ "keylabel_for_symbols_0", - /* 69 */ "additional_more_keys_for_symbols_1", - /* 70 */ "additional_more_keys_for_symbols_2", - /* 71 */ "additional_more_keys_for_symbols_3", - /* 72 */ "additional_more_keys_for_symbols_4", - /* 73 */ "additional_more_keys_for_symbols_5", - /* 74 */ "additional_more_keys_for_symbols_6", - /* 75 */ "additional_more_keys_for_symbols_7", - /* 76 */ "additional_more_keys_for_symbols_8", - /* 77 */ "additional_more_keys_for_symbols_9", - /* 78 */ "additional_more_keys_for_symbols_0", - /* 79 */ "more_keys_for_symbols_1", - /* 80 */ "more_keys_for_symbols_2", - /* 81 */ "more_keys_for_symbols_3", - /* 82 */ "more_keys_for_symbols_4", - /* 83 */ "more_keys_for_symbols_5", - /* 84 */ "more_keys_for_symbols_6", - /* 85 */ "more_keys_for_symbols_7", - /* 86 */ "more_keys_for_symbols_8", - /* 87 */ "more_keys_for_symbols_9", - /* 88 */ "more_keys_for_symbols_0", - /* 89 */ "keylabel_for_comma", - /* 90 */ "more_keys_for_comma", - /* 91 */ "keylabel_for_symbols_question", - /* 92 */ "keylabel_for_symbols_semicolon", - /* 93 */ "keylabel_for_symbols_percent", - /* 94 */ "more_keys_for_symbols_exclamation", - /* 95 */ "more_keys_for_symbols_question", - /* 96 */ "more_keys_for_symbols_semicolon", - /* 97 */ "more_keys_for_symbols_percent", - /* 98 */ "keylabel_for_tablet_comma", - /* 99 */ "keyhintlabel_for_tablet_comma", - /* 100 */ "more_keys_for_tablet_comma", - /* 101 */ "keyhintlabel_for_tablet_period", - /* 102 */ "more_keys_for_tablet_period", - /* 103 */ "keylabel_for_apostrophe", - /* 104 */ "keyhintlabel_for_apostrophe", - /* 105 */ "more_keys_for_apostrophe", - /* 106 */ "more_keys_for_q", - /* 107 */ "more_keys_for_x", - /* 108 */ "keylabel_for_q", - /* 109 */ "keylabel_for_w", - /* 110 */ "keylabel_for_y", - /* 111 */ "keylabel_for_x", - /* 112 */ "keylabel_for_spanish_row2_10", - /* 113 */ "more_keys_for_am_pm", - /* 114 */ "settings_as_more_key", - /* 115 */ "shortcut_as_more_key", - /* 116 */ "action_next_as_more_key", - /* 117 */ "action_previous_as_more_key", - /* 118 */ "label_to_more_symbol_key", - /* 119 */ "label_to_more_symbol_for_tablet_key", - /* 120 */ "label_tab_key", - /* 121 */ "label_to_phone_numeric_key", - /* 122 */ "label_to_phone_symbols_key", - /* 123 */ "label_time_am", - /* 124 */ "label_time_pm", - /* 125 */ "label_to_symbol_key_pcqwerty", - /* 126 */ "keylabel_for_popular_domain", - /* 127 */ "more_keys_for_popular_domain", - /* 128 */ "more_keys_for_smiley", + /* 46 */ "keylabel_for_currency_generic", + /* 47 */ "more_keys_for_currency_generic", + /* 48 */ "more_keys_for_punctuation", + /* 49 */ "more_keys_for_star", + /* 50 */ "more_keys_for_bullet", + /* 51 */ "more_keys_for_plus", + /* 52 */ "more_keys_for_left_parenthesis", + /* 53 */ "more_keys_for_right_parenthesis", + /* 54 */ "more_keys_for_less_than", + /* 55 */ "more_keys_for_greater_than", + /* 56 */ "more_keys_for_arabic_diacritics", + /* 57 */ "keyhintlabel_for_arabic_diacritics", + /* 58 */ "keylabel_for_symbols_1", + /* 59 */ "keylabel_for_symbols_2", + /* 60 */ "keylabel_for_symbols_3", + /* 61 */ "keylabel_for_symbols_4", + /* 62 */ "keylabel_for_symbols_5", + /* 63 */ "keylabel_for_symbols_6", + /* 64 */ "keylabel_for_symbols_7", + /* 65 */ "keylabel_for_symbols_8", + /* 66 */ "keylabel_for_symbols_9", + /* 67 */ "keylabel_for_symbols_0", + /* 68 */ "additional_more_keys_for_symbols_1", + /* 69 */ "additional_more_keys_for_symbols_2", + /* 70 */ "additional_more_keys_for_symbols_3", + /* 71 */ "additional_more_keys_for_symbols_4", + /* 72 */ "additional_more_keys_for_symbols_5", + /* 73 */ "additional_more_keys_for_symbols_6", + /* 74 */ "additional_more_keys_for_symbols_7", + /* 75 */ "additional_more_keys_for_symbols_8", + /* 76 */ "additional_more_keys_for_symbols_9", + /* 77 */ "additional_more_keys_for_symbols_0", + /* 78 */ "more_keys_for_symbols_1", + /* 79 */ "more_keys_for_symbols_2", + /* 80 */ "more_keys_for_symbols_3", + /* 81 */ "more_keys_for_symbols_4", + /* 82 */ "more_keys_for_symbols_5", + /* 83 */ "more_keys_for_symbols_6", + /* 84 */ "more_keys_for_symbols_7", + /* 85 */ "more_keys_for_symbols_8", + /* 86 */ "more_keys_for_symbols_9", + /* 87 */ "more_keys_for_symbols_0", + /* 88 */ "keylabel_for_comma", + /* 89 */ "more_keys_for_comma", + /* 90 */ "keylabel_for_symbols_question", + /* 91 */ "keylabel_for_symbols_semicolon", + /* 92 */ "keylabel_for_symbols_percent", + /* 93 */ "more_keys_for_symbols_exclamation", + /* 94 */ "more_keys_for_symbols_question", + /* 95 */ "more_keys_for_symbols_semicolon", + /* 96 */ "more_keys_for_symbols_percent", + /* 97 */ "keylabel_for_tablet_comma", + /* 98 */ "keyhintlabel_for_tablet_comma", + /* 99 */ "more_keys_for_tablet_comma", + /* 100 */ "keyhintlabel_for_tablet_period", + /* 101 */ "more_keys_for_tablet_period", + /* 102 */ "keylabel_for_apostrophe", + /* 103 */ "keyhintlabel_for_apostrophe", + /* 104 */ "more_keys_for_apostrophe", + /* 105 */ "more_keys_for_q", + /* 106 */ "more_keys_for_x", + /* 107 */ "keylabel_for_q", + /* 108 */ "keylabel_for_w", + /* 109 */ "keylabel_for_y", + /* 110 */ "keylabel_for_x", + /* 111 */ "keylabel_for_spanish_row2_10", + /* 112 */ "more_keys_for_am_pm", + /* 113 */ "settings_as_more_key", + /* 114 */ "shortcut_as_more_key", + /* 115 */ "action_next_as_more_key", + /* 116 */ "action_previous_as_more_key", + /* 117 */ "label_to_more_symbol_key", + /* 118 */ "label_to_more_symbol_for_tablet_key", + /* 119 */ "label_tab_key", + /* 120 */ "label_to_phone_numeric_key", + /* 121 */ "label_to_phone_symbols_key", + /* 122 */ "label_time_am", + /* 123 */ "label_time_pm", + /* 124 */ "label_to_symbol_key_pcqwerty", + /* 125 */ "keylabel_for_popular_domain", + /* 126 */ "more_keys_for_popular_domain", + /* 127 */ "more_keys_for_smiley", }; private static final String EMPTY = ""; @@ -258,26 +258,25 @@ public final class KeyboardTextsSet { // U+00A5: "¥" YEN SIGN // U+20B1: "₱" PESO SIGN /* 45 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1", - /* 46 */ "\u00A2,\u00A3,$,\u00A5,\u20B1", - /* 47 */ "\u00A2,$,\u20AC,\u00A5,\u20B1", - /* 48 */ "\u00A2,$,\u20AC,\u00A3,\u00A5,\u20B1", - /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)", + /* 46 */ "$", + /* 47 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1", + /* 48 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)", // U+2020: "†" DAGGER // U+2021: "‡" DOUBLE DAGGER // U+2605: "★" BLACK STAR - /* 50 */ "\u2020,\u2021,\u2605", + /* 49 */ "\u2020,\u2021,\u2605", // U+266A: "♪" EIGHTH NOTE // U+2665: "♥" BLACK HEART SUIT // U+2660: "♠" BLACK SPADE SUIT // U+2666: "♦" BLACK DIAMOND SUIT // U+2663: "♣" BLACK CLUB SUIT - /* 51 */ "\u266A,\u2665,\u2660,\u2666,\u2663", + /* 50 */ "\u266A,\u2665,\u2660,\u2666,\u2663", // U+00B1: "±" PLUS-MINUS SIGN - /* 52 */ "\u00B1", + /* 51 */ "\u00B1", // The all letters need to be mirrored are found at // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - /* 53 */ "!fixedColumnOrder!3,<,{,[", - /* 54 */ "!fixedColumnOrder!3,>,},]", + /* 52 */ "!fixedColumnOrder!3,<,{,[", + /* 53 */ "!fixedColumnOrder!3,>,},]", // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK // U+2264: "≤" LESS-THAN OR EQUAL TO @@ -293,103 +292,103 @@ public final class KeyboardTextsSet { // U+201D: "”" RIGHT DOUBLE QUOTATION MARK // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK - /* 55 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB", - /* 56 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB", + /* 54 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB", + /* 55 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB", + /* 56 */ EMPTY, /* 57 */ EMPTY, - /* 58 */ EMPTY, - /* 59 */ "1", - /* 60 */ "2", - /* 61 */ "3", - /* 62 */ "4", - /* 63 */ "5", - /* 64 */ "6", - /* 65 */ "7", - /* 66 */ "8", - /* 67 */ "9", - /* 68 */ "0", - /* 69~ */ + /* 58 */ "1", + /* 59 */ "2", + /* 60 */ "3", + /* 61 */ "4", + /* 62 */ "5", + /* 63 */ "6", + /* 64 */ "7", + /* 65 */ "8", + /* 66 */ "9", + /* 67 */ "0", + /* 68~ */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - /* ~78 */ + /* ~77 */ // U+00B9: "¹" SUPERSCRIPT ONE // U+00BD: "½" VULGAR FRACTION ONE HALF // U+2153: "⅓" VULGAR FRACTION ONE THIRD // U+00BC: "¼" VULGAR FRACTION ONE QUARTER // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH - /* 79 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B", + /* 78 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B", // U+00B2: "²" SUPERSCRIPT TWO // U+2154: "⅔" VULGAR FRACTION TWO THIRDS - /* 80 */ "\u00B2,\u2154", + /* 79 */ "\u00B2,\u2154", // U+00B3: "³" SUPERSCRIPT THREE // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS - /* 81 */ "\u00B3,\u00BE,\u215C", + /* 80 */ "\u00B3,\u00BE,\u215C", // U+2074: "⁴" SUPERSCRIPT FOUR - /* 82 */ "\u2074", + /* 81 */ "\u2074", // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS - /* 83 */ "\u215D", - /* 84 */ EMPTY, + /* 82 */ "\u215D", + /* 83 */ EMPTY, // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS - /* 85 */ "\u215E", + /* 84 */ "\u215E", + /* 85 */ EMPTY, /* 86 */ EMPTY, - /* 87 */ EMPTY, // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N // U+2205: "∅" EMPTY SET - /* 88 */ "\u207F,\u2205", - /* 89 */ ",", - /* 90 */ EMPTY, - /* 91 */ "?", - /* 92 */ ";", - /* 93 */ "%", + /* 87 */ "\u207F,\u2205", + /* 88 */ ",", + /* 89 */ EMPTY, + /* 90 */ "?", + /* 91 */ ";", + /* 92 */ "%", // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* 94 */ "\u00A1", + /* 93 */ "\u00A1", // U+00BF: "¿" INVERTED QUESTION MARK - /* 95 */ "\u00BF", - /* 96 */ EMPTY, + /* 94 */ "\u00BF", + /* 95 */ EMPTY, // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\u2030", - /* 98 */ ",", + /* 96 */ "\u2030", + /* 97 */ ",", + /* 98 */ "!", /* 99 */ "!", - /* 100 */ "!", + /* 100 */ "?", /* 101 */ "?", - /* 102 */ "?", - /* 103 */ "\'", + /* 102 */ "\'", + /* 103 */ "\"", /* 104 */ "\"", - /* 105 */ "\"", + /* 105 */ EMPTY, /* 106 */ EMPTY, - /* 107 */ EMPTY, - /* 108 */ "q", - /* 109 */ "w", - /* 110 */ "y", - /* 111 */ "x", + /* 107 */ "q", + /* 108 */ "w", + /* 109 */ "y", + /* 110 */ "x", // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE - /* 112 */ "\u00F1", - /* 113 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm", - /* 114 */ "!icon/settings_key|!code/key_settings", - /* 115 */ "!icon/shortcut_key|!code/key_shortcut", - /* 116 */ "!hasLabels!,!text/label_next_key|!code/key_action_next", - /* 117 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous", + /* 111 */ "\u00F1", + /* 112 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm", + /* 113 */ "!icon/settings_key|!code/key_settings", + /* 114 */ "!icon/shortcut_key|!code/key_shortcut", + /* 115 */ "!hasLabels!,!text/label_next_key|!code/key_action_next", + /* 116 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous", // Label for "switch to more symbol" modifier key. Must be short to fit on key! - /* 118 */ "= \\ <", + /* 117 */ "= \\ <", // Label for "switch to more symbol" modifier key on tablets. Must be short to fit on key! - /* 119 */ "~ \\ {", + /* 118 */ "~ \\ {", // Label for "Tab" key. Must be short to fit on key! - /* 120 */ "Tab", + /* 119 */ "Tab", // Label for "switch to phone numeric" key. Must be short to fit on key! - /* 121 */ "123", + /* 120 */ "123", // Label for "switch to phone symbols" key. Must be short to fit on key! // U+FF0A: "*" FULLWIDTH ASTERISK // U+FF03: "#" FULLWIDTH NUMBER SIGN - /* 122 */ "\uFF0A\uFF03", + /* 121 */ "\uFF0A\uFF03", // Key label for "ante meridiem" - /* 123 */ "AM", + /* 122 */ "AM", // Key label for "post meridiem" - /* 124 */ "PM", + /* 123 */ "PM", // Label for "switch to symbols" key on PC QWERTY layout - /* 125 */ "Sym", - /* 126 */ ".com", + /* 124 */ "Sym", + /* 125 */ ".com", // popular web domains for the locale - most popular, displayed on the keyboard - /* 127 */ "!hasLabels!,.net,.org,.gov,.edu", - /* 128 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ", + /* 126 */ "!hasLabels!,.net,.org,.gov,.edu", + /* 127 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ", }; /* Language af: Afrikaans */ @@ -458,25 +457,31 @@ public final class KeyboardTextsSet { // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, - /* ~48 */ + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + // U+FDFC: "﷼" RIAL SIGN + /* 45 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1,\uFDFC", + /* 46 */ null, + /* 47 */ null, // U+061F: "؟" ARABIC QUESTION MARK // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON - /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)", + /* 48 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)", // U+2605: "★" BLACK STAR // U+066D: "٭" ARABIC FIVE POINTED STAR - /* 50 */ "\u2605,\u066D", + /* 49 */ "\u2605,\u066D", // U+266A: "♪" EIGHTH NOTE - /* 51 */ "\u266A", - /* 52 */ null, + /* 50 */ "\u266A", + /* 51 */ null, // The all letters need to be mirrored are found at // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS - /* 53 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]", - /* 54 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[", + /* 52 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]", + /* 53 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[", // U+2264: "≤" LESS-THAN OR EQUAL TO // U+2265: "≥" GREATER-THAN EQUAL TO // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK @@ -492,8 +497,8 @@ public final class KeyboardTextsSet { // U+201D: "”" RIGHT DOUBLE QUOTATION MARK // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK - /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", - /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", + /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", + /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", // U+0655: "ٕ" ARABIC HAMZA BELOW // U+0654: "ٔ" ARABIC HAMZA ABOVE // U+0652: "ْ" ARABIC SUKUN @@ -509,64 +514,64 @@ public final class KeyboardTextsSet { // U+064E: "َ" ARABIC FATHA // U+0640: "ـ" ARABIC TATWEEL // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 57 */ "!fixedColumnOrder!7,\u0655,\u0654,\u0652,\u064D,\u064C,\u064B,\u0651,\u0656,\u0670,\u0653,\u0650,\u064F,\u064E,\u0640\u0640\u0640|\u0640", - /* 58 */ "\u0651", + /* 56 */ "!fixedColumnOrder!7,\u0655,\u0654,\u0652,\u064D,\u064C,\u064B,\u0651,\u0656,\u0670,\u0653,\u0650,\u064F,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "\u0651", // U+0661: "١" ARABIC-INDIC DIGIT ONE - /* 59 */ "\u0661", + /* 58 */ "\u0661", // U+0662: "٢" ARABIC-INDIC DIGIT TWO - /* 60 */ "\u0662", + /* 59 */ "\u0662", // U+0663: "٣" ARABIC-INDIC DIGIT THREE - /* 61 */ "\u0663", + /* 60 */ "\u0663", // U+0664: "٤" ARABIC-INDIC DIGIT FOUR - /* 62 */ "\u0664", + /* 61 */ "\u0664", // U+0665: "٥" ARABIC-INDIC DIGIT FIVE - /* 63 */ "\u0665", + /* 62 */ "\u0665", // U+0666: "٦" ARABIC-INDIC DIGIT SIX - /* 64 */ "\u0666", + /* 63 */ "\u0666", // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN - /* 65 */ "\u0667", + /* 64 */ "\u0667", // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT - /* 66 */ "\u0668", + /* 65 */ "\u0668", // U+0669: "٩" ARABIC-INDIC DIGIT NINE - /* 67 */ "\u0669", + /* 66 */ "\u0669", // U+0660: "٠" ARABIC-INDIC DIGIT ZERO - /* 68 */ "\u0660", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", + /* 67 */ "\u0660", + /* 68 */ "1", + /* 69 */ "2", + /* 70 */ "3", + /* 71 */ "4", + /* 72 */ "5", + /* 73 */ "6", + /* 74 */ "7", + /* 75 */ "8", + /* 76 */ "9", // U+066B: "٫" ARABIC DECIMAL SEPARATOR // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* 78 */ "0,\u066B,\u066C", - /* 79~ */ + /* 77 */ "0,\u066B,\u066C", + /* 78~ */ null, null, null, null, null, null, null, null, null, null, - /* ~88 */ + /* ~87 */ // U+060C: "،" ARABIC COMMA - /* 89 */ "\u060C", - /* 90 */ "\\,", - /* 91 */ "\u061F", - /* 92 */ "\u061B", + /* 88 */ "\u060C", + /* 89 */ "\\,", + /* 90 */ "\u061F", + /* 91 */ "\u061B", // U+066A: "٪" ARABIC PERCENT SIGN - /* 93 */ "\u066A", - /* 94 */ null, - /* 95 */ "?", - /* 96 */ ";", + /* 92 */ "\u066A", + /* 93 */ null, + /* 94 */ "?", + /* 95 */ ";", // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\\%,\u2030", - /* 98~ */ + /* 96 */ "\\%,\u2030", + /* 97~ */ null, null, null, null, null, - /* ~102 */ + /* ~101 */ // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON // U+061F: "؟" ARABIC QUESTION MARK - /* 103 */ "\u060C", - /* 104 */ "\u061F", - /* 105 */ "\u061F,\u061B,!,:,-,/,\',\"", + /* 102 */ "\u060C", + /* 103 */ "\u061F", + /* 104 */ "\u061F,\u061B,!,:,-,/,\',\"", }; /* Language be: Belarusian */ @@ -998,20 +1003,20 @@ public final class KeyboardTextsSet { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - /* ~105 */ - /* 106 */ "q", - /* 107 */ "x", + null, null, null, null, null, null, null, null, null, null, + /* ~104 */ + /* 105 */ "q", + /* 106 */ "x", // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX - /* 108 */ "\u015D", + /* 107 */ "\u015D", // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX - /* 109 */ "\u011D", + /* 108 */ "\u011D", // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE - /* 110 */ "\u016D", + /* 109 */ "\u016D", // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX - /* 111 */ "\u0109", + /* 110 */ "\u0109", // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX - /* 112 */ "\u0135", + /* 111 */ "\u0135", }; /* Language es: Spanish */ @@ -1069,22 +1074,25 @@ public final class KeyboardTextsSet { /* 8~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, - /* ~48 */ + null, null, null, null, null, null, null, null, null, null, + /* ~47 */ // U+00A1: "¡" INVERTED EXCLAMATION MARK // U+00BF: "¿" INVERTED QUESTION MARK - /* 49 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)", - /* 50~ */ + /* 48 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)", + /* 49~ */ null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~99 */ + /* ~98 */ // U+00A1: "¡" INVERTED EXCLAMATION MARK - /* 100 */ "!,\u00A1", - /* 101 */ null, + /* 99 */ "!,\u00A1", + /* 100 */ null, // U+00BF: "¿" INVERTED QUESTION MARK - /* 102 */ "?,\u00BF", + /* 101 */ "?,\u00BF", + /* 102 */ "\"", + /* 103 */ "\'", + /* 104 */ "\'", }; /* Language et: Estonian */ @@ -1200,25 +1208,32 @@ public final class KeyboardTextsSet { // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, - /* ~48 */ + /* 45 */ null, + // U+FDFC: "﷼" RIAL SIGN + // U+060B: "؋" AFGHANI SIGN + // U+00A2: "¢" CENT SIGN + // U+00A3: "£" POUND SIGN + // U+20AC: "€" EURO SIGN + // U+00A5: "¥" YEN SIGN + // U+20B1: "₱" PESO SIGN + /* 46 */ "\uFDFC", + /* 47 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1,\u060B", // U+061F: "؟" ARABIC QUESTION MARK // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON - /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)", + /* 48 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)", // U+2605: "★" BLACK STAR // U+066D: "٭" ARABIC FIVE POINTED STAR - /* 50 */ "\u2605,\u066D", + /* 49 */ "\u2605,\u066D", // U+266A: "♪" EIGHTH NOTE - /* 51 */ "\u266A", - /* 52 */ null, + /* 50 */ "\u266A", + /* 51 */ null, // The all letters need to be mirrored are found at // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS - /* 53 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]", - /* 54 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[", + /* 52 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]", + /* 53 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[", // U+2264: "≤" LESS-THAN OR EQUAL TO // U+2265: "≥" GREATER-THAN EQUAL TO // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK @@ -1234,8 +1249,8 @@ public final class KeyboardTextsSet { // U+201D: "”" RIGHT DOUBLE QUOTATION MARK // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK - /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>", - /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<", + /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>", + /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<", // U+0655: "ٕ" ARABIC HAMZA BELOW // U+0652: "ْ" ARABIC SUKUN // U+0651: "ّ" ARABIC SHADDA @@ -1251,68 +1266,68 @@ public final class KeyboardTextsSet { // U+064E: "َ" ARABIC FATHA // U+0640: "ـ" ARABIC TATWEEL // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label. - /* 57 */ "!fixedColumnOrder!7,\u0655,\u0652,\u0651,\u064C,\u064D,\u064B,\u0654,\u0656,\u0670,\u0653,\u064F,\u0650,\u064E,\u0640\u0640\u0640|\u0640", - /* 58 */ "\u064B", + /* 56 */ "!fixedColumnOrder!7,\u0655,\u0652,\u0651,\u064C,\u064D,\u064B,\u0654,\u0656,\u0670,\u0653,\u064F,\u0650,\u064E,\u0640\u0640\u0640|\u0640", + /* 57 */ "\u064B", // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE - /* 59 */ "\u06F1", + /* 58 */ "\u06F1", // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO - /* 60 */ "\u06F2", + /* 59 */ "\u06F2", // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE - /* 61 */ "\u06F3", + /* 60 */ "\u06F3", // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR - /* 62 */ "\u06F4", + /* 61 */ "\u06F4", // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE - /* 63 */ "\u06F5", + /* 62 */ "\u06F5", // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX - /* 64 */ "\u06F6", + /* 63 */ "\u06F6", // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN - /* 65 */ "\u06F7", + /* 64 */ "\u06F7", // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT - /* 66 */ "\u06F8", + /* 65 */ "\u06F8", // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE - /* 67 */ "\u06F9", + /* 66 */ "\u06F9", // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO - /* 68 */ "\u06F0", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", + /* 67 */ "\u06F0", + /* 68 */ "1", + /* 69 */ "2", + /* 70 */ "3", + /* 71 */ "4", + /* 72 */ "5", + /* 73 */ "6", + /* 74 */ "7", + /* 75 */ "8", + /* 76 */ "9", // U+066B: "٫" ARABIC DECIMAL SEPARATOR // U+066C: "٬" ARABIC THOUSANDS SEPARATOR - /* 78 */ "0,\u066B,\u066C", - /* 79~ */ + /* 77 */ "0,\u066B,\u066C", + /* 78~ */ null, null, null, null, null, null, null, null, null, null, - /* ~88 */ + /* ~87 */ // U+060C: "،" ARABIC COMMA - /* 89 */ "\u060C", - /* 90 */ "\\,", - /* 91 */ "\u061F", - /* 92 */ "\u061B", + /* 88 */ "\u060C", + /* 89 */ "\\,", + /* 90 */ "\u061F", + /* 91 */ "\u061B", // U+066A: "٪" ARABIC PERCENT SIGN - /* 93 */ "\u066A", - /* 94 */ null, - /* 95 */ "?", - /* 96 */ ";", + /* 92 */ "\u066A", + /* 93 */ null, + /* 94 */ "?", + /* 95 */ ";", // U+2030: "‰" PER MILLE SIGN - /* 97 */ "\\%,\u2030", + /* 96 */ "\\%,\u2030", // U+060C: "،" ARABIC COMMA // U+061B: "؛" ARABIC SEMICOLON // U+061F: "؟" ARABIC QUESTION MARK // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK - /* 98 */ "\u060C", - /* 99 */ "!", - /* 100 */ "!,\\,", - /* 101 */ "\u061F", - /* 102 */ "\u061F,?", - /* 103 */ "\u060C", - /* 104 */ "\u061F", - /* 105 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB", + /* 97 */ "\u060C", + /* 98 */ "!", + /* 99 */ "!,\\,", + /* 100 */ "\u061F", + /* 101 */ "\u061F,?", + /* 102 */ "\u060C", + /* 103 */ "\u061F", + /* 104 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB", }; /* Language fi: Finnish */ @@ -1421,38 +1436,43 @@ public final class KeyboardTextsSet { null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, null, null, null, null, null, null, - /* ~58 */ + null, + /* ~45 */ + // U+20B9: "₹" INDIAN RUPEE SIGN + /* 46 */ "\u20B9", + /* 47~ */ + null, null, null, null, null, null, null, null, null, null, null, + /* ~57 */ // U+0967: "१" DEVANAGARI DIGIT ONE - /* 59 */ "\u0967", + /* 58 */ "\u0967", // U+0968: "२" DEVANAGARI DIGIT TWO - /* 60 */ "\u0968", + /* 59 */ "\u0968", // U+0969: "३" DEVANAGARI DIGIT THREE - /* 61 */ "\u0969", + /* 60 */ "\u0969", // U+096A: "४" DEVANAGARI DIGIT FOUR - /* 62 */ "\u096A", + /* 61 */ "\u096A", // U+096B: "५" DEVANAGARI DIGIT FIVE - /* 63 */ "\u096B", + /* 62 */ "\u096B", // U+096C: "६" DEVANAGARI DIGIT SIX - /* 64 */ "\u096C", + /* 63 */ "\u096C", // U+096D: "७" DEVANAGARI DIGIT SEVEN - /* 65 */ "\u096D", + /* 64 */ "\u096D", // U+096E: "८" DEVANAGARI DIGIT EIGHT - /* 66 */ "\u096E", + /* 65 */ "\u096E", // U+096F: "९" DEVANAGARI DIGIT NINE - /* 67 */ "\u096F", + /* 66 */ "\u096F", // U+0966: "०" DEVANAGARI DIGIT ZERO - /* 68 */ "\u0966", - /* 69 */ "1", - /* 70 */ "2", - /* 71 */ "3", - /* 72 */ "4", - /* 73 */ "5", - /* 74 */ "6", - /* 75 */ "7", - /* 76 */ "8", - /* 77 */ "9", - /* 78 */ "0", + /* 67 */ "\u0966", + /* 68 */ "1", + /* 69 */ "2", + /* 70 */ "3", + /* 71 */ "4", + /* 72 */ "5", + /* 73 */ "6", + /* 74 */ "7", + /* 75 */ "8", + /* 76 */ "9", + /* 77 */ "0", }; /* Language hr: Croatian */ @@ -1648,19 +1668,21 @@ public final class KeyboardTextsSet { // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B", - /* 45~ */ - null, null, null, null, null, - /* ~49 */ + /* 45 */ null, + // U+20AA: "₪" NEW SHEQEL SIGN + /* 46 */ "\u20AA", + /* 47 */ null, + /* 48 */ null, // U+2605: "★" BLACK STAR - /* 50 */ "\u2605", - /* 51 */ null, + /* 49 */ "\u2605", + /* 50 */ null, // U+00B1: "±" PLUS-MINUS SIGN // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN - /* 52 */ "\u00B1,\uFB29", + /* 51 */ "\u00B1,\uFB29", // The all letters need to be mirrored are found at // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt - /* 53 */ "!fixedColumnOrder!3,<|>,{|},[|]", - /* 54 */ "!fixedColumnOrder!3,>|<,}|{,]|[", + /* 52 */ "!fixedColumnOrder!3,<|>,{|},[|]", + /* 53 */ "!fixedColumnOrder!3,>|<,}|{,]|[", // U+2264: "≤" LESS-THAN OR EQUAL TO // U+2265: "≥" GREATER-THAN EQUAL TO // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK @@ -1676,8 +1698,8 @@ public final class KeyboardTextsSet { // U+201D: "”" RIGHT DOUBLE QUOTATION MARK // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK - /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", - /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", + /* 54 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB", + /* 55 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB", }; /* Language ky: Kirghiz */ @@ -1928,6 +1950,18 @@ public final class KeyboardTextsSet { /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B", }; + /* Language mn: Mongolian */ + private static final String[] LANGUAGE_mn = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, + /* ~45 */ + // U+20AE: "₮" TUGRIK SIGN + /* 46 */ "\u20AE", + }; + /* Language nb: Norwegian Bokmål */ private static final String[] LANGUAGE_nb = { // U+00E0: "à" LATIN SMALL LETTER A WITH GRAVE @@ -2465,6 +2499,18 @@ public final class KeyboardTextsSet { /* 15 */ "g\'", }; + /* Language th: Thai */ + private static final String[] LANGUAGE_th = { + /* 0~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, + /* ~45 */ + // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT + /* 46 */ "\u0E3F", + }; + /* Language tl: Tagalog */ private static final String[] LANGUAGE_tl = { // U+00E1: "á" LATIN SMALL LETTER A WITH ACUTE @@ -2589,6 +2635,11 @@ public final class KeyboardTextsSet { /* 34 */ null, // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN /* 35 */ "\u044A", + /* 36~ */ + null, null, null, null, null, null, null, null, null, null, + /* ~45 */ + // U+20B4: "₴" HRYVNIA SIGN + /* 46 */ "\u20B4", }; /* Language vi: Vietnamese */ @@ -2670,6 +2721,13 @@ public final class KeyboardTextsSet { /* 8 */ "\u1EF3,\u00FD,\u1EF7,\u1EF9,\u1EF5", // U+0111: "đ" LATIN SMALL LETTER D WITH STROKE /* 9 */ "\u0111", + /* 10~ */ + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, + /* ~45 */ + // U+20AB: "₫" DONG SIGN + /* 46 */ "\u20AB", }; /* Language zu: Zulu */ @@ -2868,6 +2926,7 @@ public final class KeyboardTextsSet { "lt", LANGUAGE_lt, /* Lithuanian */ "lv", LANGUAGE_lv, /* Latvian */ "mk", LANGUAGE_mk, /* Macedonian */ + "mn", LANGUAGE_mn, /* Mongolian */ "nb", LANGUAGE_nb, /* Norwegian Bokmål */ "nl", LANGUAGE_nl, /* Dutch */ "pl", LANGUAGE_pl, /* Polish */ @@ -2880,6 +2939,7 @@ public final class KeyboardTextsSet { "sr", LANGUAGE_sr, /* Serbian */ "sv", LANGUAGE_sv, /* Swedish */ "sw", LANGUAGE_sw, /* Swahili */ + "th", LANGUAGE_th, /* Thai */ "tl", LANGUAGE_tl, /* Tagalog */ "tr", LANGUAGE_tr, /* Turkish */ "uk", LANGUAGE_uk, /* Ukrainian */ diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index 550391b49..ca16163d4 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.StringUtils; import java.util.Locale; @@ -35,10 +35,10 @@ public final class MoreKeySpec { KeySpecParser.getLabel(moreKeySpec), needsToUpperCase, locale); final int code = KeySpecParser.toUpperCaseOfCodeForLocale( KeySpecParser.getCode(moreKeySpec, codesSet), needsToUpperCase, locale); - if (code == Keyboard.CODE_UNSPECIFIED) { + if (code == Constants.CODE_UNSPECIFIED) { // Some letter, for example German Eszett (U+00DF: "ß"), has multiple characters // upper case representation ("SS"). - mCode = Keyboard.CODE_OUTPUT_TEXT; + mCode = Constants.CODE_OUTPUT_TEXT; mOutputText = mLabel; } else { mCode = code; @@ -75,8 +75,8 @@ public final class MoreKeySpec { public String toString() { final String label = (mIconId == KeyboardIconsSet.ICON_UNDEFINED ? mLabel : KeySpecParser.PREFIX_ICON + KeyboardIconsSet.getIconName(mIconId)); - final String output = (mCode == Keyboard.CODE_OUTPUT_TEXT ? mOutputText - : Keyboard.printableCode(mCode)); + final String output = (mCode == Constants.CODE_OUTPUT_TEXT ? mOutputText + : Constants.printableCode(mCode)); if (StringUtils.codePointCount(label) == 1 && label.codePointAt(0) == mCode) { return output; } else { diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java index d8950a713..d7a2b6f39 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchPositionCorrection.java @@ -16,6 +16,7 @@ package com.android.inputmethod.keyboard.internal; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.LatinImeLogger; public final class TouchPositionCorrection { @@ -66,7 +67,7 @@ public final class TouchPositionCorrection { } } - // For test only + @UsedForTesting public void setEnabled(final boolean enabled) { mEnabled = enabled; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java index c53428fe5..c795d5322 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SuddenJumpingTouchEventHandler.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java @@ -20,7 +20,6 @@ import android.content.Context; import android.util.Log; import android.view.MotionEvent; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -28,8 +27,8 @@ import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger; -public final class SuddenJumpingTouchEventHandler { - private static final String TAG = SuddenJumpingTouchEventHandler.class.getSimpleName(); +public final class TouchScreenRegulator { + private static final String TAG = TouchScreenRegulator.class.getSimpleName(); private static boolean DEBUG_MODE = LatinImeLogger.sDBG; public interface ProcessMotionEvent { @@ -50,17 +49,18 @@ public final class SuddenJumpingTouchEventHandler { private int mJumpThresholdSquare = Integer.MAX_VALUE; private int mLastX; private int mLastY; + // One-seventh of the keyboard width seems like a reasonable threshold + private static final float JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH = 1.0f / 7.0f; - public SuddenJumpingTouchEventHandler(Context context, ProcessMotionEvent view) { + public TouchScreenRegulator(final Context context, final ProcessMotionEvent view) { mView = view; mNeedsSuddenJumpingHack = Boolean.parseBoolean(ResourceUtils.getDeviceOverrideValue( context.getResources(), R.array.sudden_jumping_touch_event_device_list, "false")); } - public void setKeyboard(Keyboard newKeyboard) { - // One-seventh of the keyboard width seems like a reasonable threshold - final int jumpThreshold = newKeyboard.mOccupiedWidth / 7; - mJumpThresholdSquare = jumpThreshold * jumpThreshold; + public void setKeyboardGeometry(final int keyboardWidth) { + final float jumpThreshold = keyboardWidth * JUMP_THRESHOLD_RATIO_TO_KEYBOARD_WIDTH; + mJumpThresholdSquare = (int)(jumpThreshold * jumpThreshold); } /** @@ -74,7 +74,7 @@ public final class SuddenJumpingTouchEventHandler { * @return true if the event was consumed, so that it doesn't continue to be handled by * {@link MainKeyboardView}. */ - private boolean handleSuddenJumping(MotionEvent me) { + private boolean handleSuddenJumping(final MotionEvent me) { if (!mNeedsSuddenJumpingHack) return false; final int action = me.getAction(); @@ -140,7 +140,7 @@ public final class SuddenJumpingTouchEventHandler { return result; } - public boolean onTouchEvent(MotionEvent me) { + public boolean onTouchEvent(final MotionEvent me) { // If there was a sudden jump, return without processing the actual motion event. if (handleSuddenJumping(me)) { if (DEBUG_MODE) diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java index 939451899..96c08b3cd 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java @@ -50,6 +50,7 @@ import java.util.ArrayList; import java.util.TreeSet; public final class AdditionalSubtypeSettings extends PreferenceFragment { + private RichInputMethodManager mRichImm; private SharedPreferences mPrefs; private SubtypeLocaleAdapter mSubtypeLocaleAdapter; private KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; @@ -63,6 +64,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private static final String KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN = "is_subtype_enabler_notification_dialog_open"; private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler"; + static final class SubtypeLocaleItem extends Pair<String, String> implements Comparable<SubtypeLocaleItem> { public SubtypeLocaleItem(final String localeString, final String displayName) { @@ -93,7 +95,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet(); - final InputMethodInfo imi = ImfUtils.getInputMethodInfoOfThisIme(context); + final InputMethodInfo imi = RichInputMethodManager.getInstance() + .getInputMethodInfoOfThisIme(); final int count = imi.getSubtypeCount(); for (int i = 0; i < count; i++) { final InputMethodSubtype subtype = imi.getSubtypeAt(i); @@ -383,6 +386,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); + RichInputMethodManager.init(getActivity()); + mRichImm = RichInputMethodManager.getInstance(); addPreferencesFromResource(R.xml.additional_subtype_settings); setHasOptionsMenu(true); @@ -439,7 +444,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { mIsAddingNewSubtype = false; final PreferenceGroup group = getPreferenceScreen(); group.removePreference(subtypePref); - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); } @Override @@ -449,7 +454,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { return; } if (findDuplicatedSubtype(subtype) == null) { - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); return; } @@ -466,7 +471,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { mIsAddingNewSubtype = false; final InputMethodSubtype subtype = subtypePref.getSubtype(); if (findDuplicatedSubtype(subtype) == null) { - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), getSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); mSubtypePreferenceKeyForSubtypeEnabler = subtypePref.getKey(); mSubtypeEnablerNotificationDialog = createDialog(subtypePref); mSubtypeEnablerNotificationDialog.show(); @@ -501,8 +506,8 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) { final String localeString = subtype.getLocale(); final String keyboardLayoutSetName = SubtypeLocale.getKeyboardLayoutSetName(subtype); - return ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - getActivity(), localeString, keyboardLayoutSetName); + return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + localeString, keyboardLayoutSetName); } private AlertDialog createDialog( @@ -515,7 +520,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { @Override public void onClick(DialogInterface dialog, int which) { final Intent intent = CompatUtils.getInputLanguageSelectionIntent( - ImfUtils.getInputMethodIdOfThisIme(getActivity()), + mRichImm.getInputMethodIdOfThisIme(), Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -573,7 +578,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { } finally { editor.apply(); } - ImfUtils.setAdditionalInputMethodSubtypes(getActivity(), subtypes); + mRichImm.setAdditionalInputMethodSubtypes(subtypes); } @Override diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java index 59ef5e09f..024726391 100644 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java @@ -21,7 +21,6 @@ import android.media.AudioManager; import android.view.HapticFeedbackConstants; import android.view.View; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.VibratorUtils; /** @@ -31,17 +30,15 @@ import com.android.inputmethod.latin.VibratorUtils; * complexity of settings and the like. */ public final class AudioAndHapticFeedbackManager { - final private SettingsValues mSettingsValues; - final private AudioManager mAudioManager; - final private VibratorUtils mVibratorUtils; + private final AudioManager mAudioManager; + private final VibratorUtils mVibratorUtils; + + private SettingsValues mSettingsValues; private boolean mSoundOn; - public AudioAndHapticFeedbackManager(final LatinIME latinIme, - final SettingsValues settingsValues) { - mSettingsValues = settingsValues; + public AudioAndHapticFeedbackManager(final LatinIME latinIme) { mVibratorUtils = VibratorUtils.getInstance(latinIme); mAudioManager = (AudioManager) latinIme.getSystemService(Context.AUDIO_SERVICE); - mSoundOn = reevaluateIfSoundIsOn(); } public void hapticAndAudioFeedback(final int primaryCode, @@ -51,7 +48,7 @@ public final class AudioAndHapticFeedbackManager { } private boolean reevaluateIfSoundIsOn() { - if (!mSettingsValues.mSoundOn || mAudioManager == null) { + if (mSettingsValues == null || !mSettingsValues.mSoundOn || mAudioManager == null) { return false; } else { return mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL; @@ -64,13 +61,13 @@ public final class AudioAndHapticFeedbackManager { if (mSoundOn) { final int sound; switch (primaryCode) { - case Keyboard.CODE_DELETE: + case Constants.CODE_DELETE: sound = AudioManager.FX_KEYPRESS_DELETE; break; - case Keyboard.CODE_ENTER: + case Constants.CODE_ENTER: sound = AudioManager.FX_KEYPRESS_RETURN; break; - case Keyboard.CODE_SPACE: + case Constants.CODE_SPACE: sound = AudioManager.FX_KEYPRESS_SPACEBAR; break; default: @@ -81,8 +78,7 @@ public final class AudioAndHapticFeedbackManager { } } - // TODO: make this private when LatinIME does not call it any more - public void vibrate(final View viewToPerformHapticFeedbackOn) { + private void vibrate(final View viewToPerformHapticFeedbackOn) { if (!mSettingsValues.mVibrateOn) { return; } @@ -98,6 +94,11 @@ public final class AudioAndHapticFeedbackManager { } } + public void onSettingsChanged(final SettingsValues settingsValues) { + mSettingsValues = settingsValues; + mSoundOn = reevaluateIfSoundIsOn(); + } + public void onRingerModeChanged() { mSoundOn = reevaluateIfSoundIsOn(); } diff --git a/java/src/com/android/inputmethod/latin/AutoCorrection.java b/java/src/com/android/inputmethod/latin/AutoCorrection.java index 84fad158f..fa35922b0 100644 --- a/java/src/com/android/inputmethod/latin/AutoCorrection.java +++ b/java/src/com/android/inputmethod/latin/AutoCorrection.java @@ -33,11 +33,11 @@ public final class AutoCorrection { } public static boolean isValidWord(final ConcurrentHashMap<String, Dictionary> dictionaries, - CharSequence word, boolean ignoreCase) { + final String word, final boolean ignoreCase) { if (TextUtils.isEmpty(word)) { return false; } - final CharSequence lowerCasedWord = word.toString().toLowerCase(); + final String lowerCasedWord = word.toLowerCase(); for (final String key : dictionaries.keySet()) { final Dictionary dictionary = dictionaries.get(key); // It's unclear how realistically 'dictionary' can be null, but the monkey is somehow @@ -57,7 +57,7 @@ public final class AutoCorrection { } public static int getMaxFrequency(final ConcurrentHashMap<String, Dictionary> dictionaries, - CharSequence word) { + final String word) { if (TextUtils.isEmpty(word)) { return Dictionary.NOT_A_PROBABILITY; } @@ -76,12 +76,13 @@ public final class AutoCorrection { // Returns true if this is in any of the dictionaries. public static boolean isInTheDictionary( final ConcurrentHashMap<String, Dictionary> dictionaries, - final CharSequence word, final boolean ignoreCase) { + final String word, final boolean ignoreCase) { return isValidWord(dictionaries, word, ignoreCase); } - public static boolean suggestionExceedsAutoCorrectionThreshold(SuggestedWordInfo suggestion, - CharSequence consideredWord, float autoCorrectionThreshold) { + public static boolean suggestionExceedsAutoCorrectionThreshold( + final SuggestedWordInfo suggestion, final String consideredWord, + final float autoCorrectionThreshold) { if (null != suggestion) { // Shortlist a whitelisted word if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true; @@ -89,8 +90,7 @@ public final class AutoCorrection { // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. final float normalizedScore = BinaryDictionary.calcNormalizedScore( - consideredWord.toString(), suggestion.mWord.toString(), - autoCorrectionSuggestionScore); + consideredWord, suggestion.mWord, autoCorrectionSuggestionScore); if (DBG) { Log.d(TAG, "Normalized " + consideredWord + "," + suggestion + "," + autoCorrectionSuggestionScore + ", " + normalizedScore @@ -100,8 +100,7 @@ public final class AutoCorrection { if (DBG) { Log.d(TAG, "Auto corrected by S-threshold."); } - return !shouldBlockAutoCorrectionBySafetyNet(consideredWord.toString(), - suggestion.mWord); + return !shouldBlockAutoCorrectionBySafetyNet(consideredWord, suggestion.mWord); } } return false; @@ -110,7 +109,7 @@ public final class AutoCorrection { // TODO: Resolve the inconsistencies between the native auto correction algorithms and // this safety net public static boolean shouldBlockAutoCorrectionBySafetyNet(final String typedWord, - final CharSequence suggestion) { + final String suggestion) { // Safety net for auto correction. // Actually if we hit this safety net, it's a bug. // If user selected aggressive auto correction mode, there is no need to use the safety @@ -123,7 +122,7 @@ public final class AutoCorrection { } final int maxEditDistanceOfNativeDictionary = (typedWordLength < 5 ? 2 : typedWordLength / 2) + 1; - final int distance = BinaryDictionary.editDistance(typedWord, suggestion.toString()); + final int distance = BinaryDictionary.editDistance(typedWord, suggestion); if (DBG) { Log.d(TAG, "Autocorrected edit distance = " + distance + ", " + maxEditDistanceOfNativeDictionary); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 7184f1d8a..6048a3308 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -31,7 +31,7 @@ import java.util.Locale; * Implements a static, compacted, binary dictionary of standard words. */ public final class BinaryDictionary extends Dictionary { - + private static final String TAG = BinaryDictionary.class.getSimpleName(); public static final String DICTIONARY_PACK_AUTHORITY = "com.android.inputmethod.latin.dictionarypack"; @@ -45,17 +45,13 @@ public final class BinaryDictionary extends Dictionary { public static final int MAX_WORDS = 18; public static final int MAX_SPACES = 16; - private static final String TAG = BinaryDictionary.class.getSimpleName(); private static final int MAX_PREDICTIONS = 60; private static final int MAX_RESULTS = Math.max(MAX_PREDICTIONS, MAX_WORDS); - private static final int TYPED_LETTER_MULTIPLIER = 2; - private long mNativeDict; private final Locale mLocale; private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; - // TODO: The below should be int[] mOutputCodePoints - private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_RESULTS]; + private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; private final int[] mSpaceIndices = new int[MAX_SPACES]; private final int[] mOutputScores = new int[MAX_RESULTS]; private final int[] mOutputTypes = new int[MAX_RESULTS]; @@ -67,7 +63,7 @@ public final class BinaryDictionary extends Dictionary { // TODO: There should be a way to remove used DicTraverseSession objects from // {@code mDicTraverseSessions}. - private DicTraverseSession getTraverseSession(int traverseSessionId) { + private DicTraverseSession getTraverseSession(final int traverseSessionId) { synchronized(mDicTraverseSessions) { DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId); if (traverseSession == null) { @@ -84,7 +80,6 @@ public final class BinaryDictionary extends Dictionary { /** * Constructor for the binary dictionary. This is supposed to be called from the * dictionary factory. - * All implementations should pass null into flagArray, except for testing purposes. * @param context the context to access the environment from. * @param filename the name of the file to read through native code. * @param offset the offset of the dictionary data within the file. @@ -92,9 +87,9 @@ public final class BinaryDictionary extends Dictionary { * @param useFullEditDistance whether to use the full edit distance in suggestions * @param dictType the dictionary type, as a human-readable string */ - public BinaryDictionary(final Context context, - final String filename, final long offset, final long length, - final boolean useFullEditDistance, final Locale locale, final String dictType) { + public BinaryDictionary(final Context context, final String filename, final long offset, + final long length, final boolean useFullEditDistance, final Locale locale, + final String dictType) { super(dictType); mLocale = locale; mUseFullEditDistance = useFullEditDistance; @@ -106,40 +101,40 @@ public final class BinaryDictionary extends Dictionary { } private native long openNative(String sourceDir, long dictOffset, long dictSize, - int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords, - int maxPredictions); + int maxWordLength, int maxWords, int maxPredictions); private native void closeNative(long dict); private native int getFrequencyNative(long dict, int[] word); private native boolean isValidBigramNative(long dict, int[] word1, int[] word2); private native int getSuggestionsNative(long dict, long proximityInfo, long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int codesSize, int commitPoint, boolean isGesture, - int[] prevWordCodePointArray, boolean useFullEditDistance, char[] outputChars, + int[] prevWordCodePointArray, boolean useFullEditDistance, int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes); - private static native float calcNormalizedScoreNative(char[] before, char[] after, int score); - private static native int editDistanceNative(char[] before, char[] after); + private static native float calcNormalizedScoreNative(int[] before, int[] after, int score); + private static native int editDistanceNative(int[] before, int[] after); // TODO: Move native dict into session - private final void loadDictionary(String path, long startOffset, long length) { - mNativeDict = openNative(path, startOffset, length, TYPED_LETTER_MULTIPLIER, - FULL_WORD_SCORE_MULTIPLIER, MAX_WORD_LENGTH, MAX_WORDS, MAX_PREDICTIONS); + private final void loadDictionary(final String path, final long startOffset, + final long length) { + mNativeDict = openNative(path, startOffset, length, MAX_WORD_LENGTH, MAX_WORDS, + MAX_PREDICTIONS); } @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, 0); } @Override public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) { + final String prevWord, final ProximityInfo proximityInfo, int sessionId) { if (!isValidDictionary()) return null; Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE); // TODO: toLowerCase in the native code final int[] prevWordCodePointArray = (null == prevWord) - ? null : StringUtils.toCodePointArray(prevWord.toString()); + ? null : StringUtils.toCodePointArray(prevWord); final int composerSize = composer.size(); final boolean isGesture = composer.isBatchMode(); @@ -157,7 +152,8 @@ public final class BinaryDictionary extends Dictionary { proximityInfo.getNativeProximityInfo(), getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, codesSize, 0 /* commitPoint */, isGesture, prevWordCodePointArray, - mUseFullEditDistance, mOutputChars, mOutputScores, mSpaceIndices, mOutputTypes); + mUseFullEditDistance, mOutputCodePoints, mOutputScores, mSpaceIndices, + mOutputTypes); final int count = Math.min(tmpCount, MAX_PREDICTIONS); final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); @@ -165,53 +161,56 @@ public final class BinaryDictionary extends Dictionary { if (composerSize > 0 && mOutputScores[j] < 1) break; final int start = j * MAX_WORD_LENGTH; int len = 0; - while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { + while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) { ++len; } if (len > 0) { final int score = SuggestedWordInfo.KIND_WHITELIST == mOutputTypes[j] ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j]; - suggestions.add(new SuggestedWordInfo( - new String(mOutputChars, start, len), score, mOutputTypes[j], mDictType)); + suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len), + score, mOutputTypes[j], mDictType)); } } return suggestions; } - /* package for test */ boolean isValidDictionary() { + public boolean isValidDictionary() { return mNativeDict != 0; } - public static float calcNormalizedScore(String before, String after, int score) { - return calcNormalizedScoreNative(before.toCharArray(), after.toCharArray(), score); + public static float calcNormalizedScore(final String before, final String after, + final int score) { + return calcNormalizedScoreNative(StringUtils.toCodePointArray(before), + StringUtils.toCodePointArray(after), score); } - public static int editDistance(String before, String after) { + public static int editDistance(final String before, final String after) { if (before == null || after == null) { throw new IllegalArgumentException(); } - return editDistanceNative(before.toCharArray(), after.toCharArray()); + return editDistanceNative(StringUtils.toCodePointArray(before), + StringUtils.toCodePointArray(after)); } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { return getFrequency(word) >= 0; } @Override - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { if (word == null) return -1; - int[] codePoints = StringUtils.toCodePointArray(word.toString()); + int[] codePoints = StringUtils.toCodePointArray(word); return getFrequencyNative(mNativeDict, codePoints); } // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni // calls when checking for changes in an entire dictionary. - public boolean isValidBigram(CharSequence word1, CharSequence word2) { + public boolean isValidBigram(final String word1, final String word2) { if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false; - int[] chars1 = StringUtils.toCodePointArray(word1.toString()); - int[] chars2 = StringUtils.toCodePointArray(word2.toString()); - return isValidBigramNative(mNativeDict, chars1, chars2); + final int[] codePoints1 = StringUtils.toCodePointArray(word1); + final int[] codePoints2 = StringUtils.toCodePointArray(word2); + return isValidBigramNative(mNativeDict, codePoints1, codePoints2); } @Override diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 9a3f88f52..5eab292fc 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -149,7 +149,13 @@ public final class BinaryDictionaryFileDumper { final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id); final String finalFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context); - final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context); + String tempFileName; + try { + tempFileName = BinaryDictionaryGetter.getTempFileName(id, context); + } catch (IOException e) { + Log.e(TAG, "Can't open the temporary file", e); + return null; + } for (int mode = MODE_MIN; mode <= MODE_MAX; ++mode) { InputStream originalSourceStream = null; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index c747dc673..83dabbede 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.FormatSpec; @@ -165,14 +166,9 @@ final class BinaryDictionaryGetter { /** * Generates a unique temporary file name in the app cache directory. - * - * This is unique as long as it doesn't get called twice in the same millisecond by the same - * thread, which should be more than enough for our purposes. */ - public static String getTempFileName(String id, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return context.getCacheDir() + File.separator + fileName + "." - + Thread.currentThread().getId() + "." + System.currentTimeMillis(); + public static String getTempFileName(String id, Context context) throws IOException { + return File.createTempFile(replaceFileNameDangerousCharacters(id), null).getAbsolutePath(); } /** @@ -427,8 +423,11 @@ final class BinaryDictionaryGetter { // cacheWordListsFromContentProvider returns the list of files it copied to local // storage, but we don't really care about what was copied NOW: what we want is the // list of everything we ever cached, so we ignore the return value. - BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, - hasDefaultWordList); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet + if (!ProductionFlag.IS_EXPERIMENTAL) { + BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, + hasDefaultWordList); + } final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); final String mainDictId = getMainDictId(locale); final DictPackSettings dictPackSettings = new DictPackSettings(context); diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 57e12a64f..ba932e590 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -136,11 +136,81 @@ public final class Constants { public static final int NOT_A_CODE = -1; - // See {@link KeyboardActionListener.Adapter#isInvalidCoordinate(int)}. public static final int NOT_A_COORDINATE = -1; public static final int SUGGESTION_STRIP_COORDINATE = -2; public static final int SPELL_CHECKER_COORDINATE = -3; + public static boolean isValidCoordinate(final int coordinate) { + // Detect {@link NOT_A_COORDINATE}, {@link SUGGESTION_STRIP_COORDINATE}, + // and {@link SPELL_CHECKER_COORDINATE}. + return coordinate >= 0; + } + + /** + * Some common keys code. Must be positive. + */ + public static final int CODE_ENTER = '\n'; + public static final int CODE_TAB = '\t'; + public static final int CODE_SPACE = ' '; + public static final int CODE_PERIOD = '.'; + public static final int CODE_DASH = '-'; + public static final int CODE_SINGLE_QUOTE = '\''; + public static final int CODE_DOUBLE_QUOTE = '"'; + public static final int CODE_QUESTION_MARK = '?'; + public static final int CODE_EXCLAMATION_MARK = '!'; + // TODO: Check how this should work for right-to-left languages. It seems to stand + // that for rtl languages, a closing parenthesis is a left parenthesis. Is this + // managed by the font? Or is it a different char? + public static final int CODE_CLOSING_PARENTHESIS = ')'; + public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; + public static final int CODE_CLOSING_CURLY_BRACKET = '}'; + public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; + + /** + * Special keys code. Must be negative. + * These should be aligned with KeyboardCodesSet.ID_TO_NAME[], + * KeyboardCodesSet.DEFAULT[] and KeyboardCodesSet.RTL[] + */ + public static final int CODE_SHIFT = -1; + public static final int CODE_SWITCH_ALPHA_SYMBOL = -2; + public static final int CODE_OUTPUT_TEXT = -3; + public static final int CODE_DELETE = -4; + public static final int CODE_SETTINGS = -5; + public static final int CODE_SHORTCUT = -6; + public static final int CODE_ACTION_ENTER = -7; + public static final int CODE_ACTION_NEXT = -8; + public static final int CODE_ACTION_PREVIOUS = -9; + public static final int CODE_LANGUAGE_SWITCH = -10; + public static final int CODE_RESEARCH = -11; + // Code value representing the code is not specified. + public static final int CODE_UNSPECIFIED = -12; + + public static boolean isLetterCode(final int code) { + return code >= CODE_SPACE; + } + + public static String printableCode(final int code) { + switch (code) { + case CODE_SHIFT: return "shift"; + case CODE_SWITCH_ALPHA_SYMBOL: return "symbol"; + case CODE_OUTPUT_TEXT: return "text"; + case CODE_DELETE: return "delete"; + case CODE_SETTINGS: return "settings"; + case CODE_SHORTCUT: return "shortcut"; + case CODE_ACTION_ENTER: return "actionEnter"; + case CODE_ACTION_NEXT: return "actionNext"; + case CODE_ACTION_PREVIOUS: return "actionPrevious"; + case CODE_LANGUAGE_SWITCH: return "languageSwitch"; + case CODE_UNSPECIFIED: return "unspec"; + case CODE_TAB: return "tab"; + case CODE_ENTER: return "enter"; + default: + if (code < CODE_SPACE) return String.format("'\\u%02x'", code); + if (code < 0x100) return String.format("'%c'", code); + return String.format("'\\u%04x'", code); + } + } + private Constants() { // This utility class is not publicly instantiable. } diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 5edc4314f..d1b32a2f3 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -24,8 +24,6 @@ import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.keyboard.Keyboard; - import java.util.Locale; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { @@ -62,7 +60,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { */ private final boolean mUseFirstLastBigrams; - public ContactsBinaryDictionary(final Context context, Locale locale) { + public ContactsBinaryDictionary(final Context context, final Locale locale) { super(context, getFilenameWithLocale(NAME, locale.toString()), Dictionary.TYPE_CONTACTS); mLocale = locale; mUseFirstLastBigrams = useFirstLastBigramsForLocale(locale); @@ -120,7 +118,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } } - private boolean useFirstLastBigramsForLocale(Locale locale) { + private boolean useFirstLastBigramsForLocale(final Locale locale) { // TODO: Add firstname/lastname bigram rules for other languages. if (locale != null && locale.getLanguage().equals(Locale.ENGLISH.getLanguage())) { return true; @@ -128,7 +126,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { return false; } - private void addWords(Cursor cursor) { + private void addWords(final Cursor cursor) { clearFusionDictionary(); int count = 0; while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { @@ -160,7 +158,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their * bigrams depending on locale. */ - private void addName(String name) { + private void addName(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; // TODO: Better tokenization for non-Latin writing systems @@ -188,12 +186,13 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { /** * Returns the index of the last letter in the word, starting from position startIndex. */ - private static int getWordEndPosition(String string, int len, int startIndex) { + private static int getWordEndPosition(final String string, final int len, + final int startIndex) { int end; int cp = 0; for (end = startIndex + 1; end < len; end += Character.charCount(cp)) { cp = string.codePointAt(end); - if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE + if (!(cp == Constants.CODE_DASH || cp == Constants.CODE_SINGLE_QUOTE || Character.isLetter(cp))) { break; } @@ -249,7 +248,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { return false; } - private static boolean isValidName(String name) { + private static boolean isValidName(final String name) { if (name != null && -1 == name.indexOf('@')) { return true; } @@ -259,7 +258,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { /** * Checks if the words in a name are in the current binary dictionary. */ - private boolean isNameInDictionary(String name) { + private boolean isNameInDictionary(final String name) { int len = StringUtils.codePointCount(name); String prevWord = null; for (int i = 0; i < len; i++) { diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 3af3cab2c..731b9baf5 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -37,6 +37,8 @@ public final class DebugSettings extends PreferenceFragment private static final String DEBUG_MODE_KEY = "debug_mode"; public static final String FORCE_NON_DISTINCT_MULTITOUCH_KEY = "force_non_distinct_multitouch"; public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; + private static final String PREF_STATISTICS_LOGGING_KEY = "enable_logging"; + private static final boolean SHOW_STATISTICS_LOGGING = false; private boolean mServiceNeedsRestart = false; private CheckBoxPreference mDebugMode; @@ -55,6 +57,12 @@ public final class DebugSettings extends PreferenceFragment ResearchLogger.DEFAULT_USABILITY_STUDY_MODE)); checkbox.setSummary(R.string.settings_warning_researcher_mode); } + final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING_KEY); + if (statisticsLoggingPref instanceof CheckBoxPreference) { + if (!SHOW_STATISTICS_LOGGING) { + getPreferenceScreen().removePreference(statisticsLoggingPref); + } + } mServiceNeedsRestart = false; mDebugMode = (CheckBoxPreference) findPreference(DEBUG_MODE_KEY); diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 88d0c09dd..a218509f3 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -26,11 +26,6 @@ import java.util.ArrayList; * strokes. */ public abstract class Dictionary { - /** - * The weight to give to a word if it's length is the same as the number of typed characters. - */ - protected static final int FULL_WORD_SCORE_MULTIPLIER = 2; - public static final int NOT_A_PROBABILITY = -1; public static final String TYPE_USER_TYPED = "user_typed"; @@ -59,12 +54,12 @@ public abstract class Dictionary { // TODO: pass more context than just the previous word, to enable better suggestions (n-gram // and more) abstract public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo); + final String prevWord, final ProximityInfo proximityInfo); // The default implementation of this method ignores sessionId. // Subclasses that want to use sessionId need to override this method. public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo, int sessionId) { + final String prevWord, final ProximityInfo proximityInfo, final int sessionId) { return getSuggestions(composer, prevWord, proximityInfo); } @@ -73,9 +68,9 @@ public abstract class Dictionary { * @param word the word to search for. The search should be case-insensitive. * @return true if the word exists, false otherwise */ - abstract public boolean isValidWord(CharSequence word); + abstract public boolean isValidWord(final String word); - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { return NOT_A_PROBABILITY; } @@ -87,7 +82,7 @@ public abstract class Dictionary { * @param typedWord the word to compare with * @return true if they are the same, false otherwise. */ - protected boolean same(final char[] word, final int length, final CharSequence typedWord) { + protected boolean same(final char[] word, final int length, final String typedWord) { if (typedWord.length() != length) { return false; } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index d3b120989..7f78ac8a2 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -56,7 +56,7 @@ public final class DictionaryCollection extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { final CopyOnWriteArrayList<Dictionary> dictionaries = mDictionaries; if (dictionaries.isEmpty()) return null; // To avoid creating unnecessary objects, we get the list out of the first @@ -74,14 +74,14 @@ public final class DictionaryCollection extends Dictionary { } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { for (int i = mDictionaries.size() - 1; i >= 0; --i) if (mDictionaries.get(i).isValidWord(word)) return true; return false; } @Override - public int getFrequency(CharSequence word) { + public int getFrequency(final String word) { int maxFreq = -1; for (int i = mDictionaries.size() - 1; i >= 0; --i) { final int tempFreq = mDictionaries.get(i).getFrequency(word); diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index b93c17f11..159867ade 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -198,7 +198,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { asyncReloadDictionaryIfRequired(); if (mLocalDictionaryController.tryLock()) { try { @@ -213,12 +213,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } @Override - public boolean isValidWord(final CharSequence word) { + public boolean isValidWord(final String word) { asyncReloadDictionaryIfRequired(); return isValidWordInner(word); } - protected boolean isValidWordInner(final CharSequence word) { + protected boolean isValidWordInner(final String word) { if (mLocalDictionaryController.tryLock()) { try { return isValidWordLocked(word); @@ -229,17 +229,17 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } - protected boolean isValidWordLocked(final CharSequence word) { + protected boolean isValidWordLocked(final String word) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidWord(word); } - protected boolean isValidBigram(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigram(final String word1, final String word2) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidBigram(word1, word2); } - protected boolean isValidBigramInner(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigramInner(final String word1, final String word2) { if (mLocalDictionaryController.tryLock()) { try { return isValidBigramLocked(word1, word2); @@ -250,7 +250,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } - protected boolean isValidBigramLocked(final CharSequence word1, final CharSequence word2) { + protected boolean isValidBigramLocked(final String word1, final String word2) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidBigram(word1, word2); } diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 8cdc2a0af..fa0d5497b 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; @@ -32,6 +31,10 @@ import java.util.LinkedList; * be searched for suggestions and valid words. */ public class ExpandableDictionary extends Dictionary { + /** + * The weight to give to a word if it's length is the same as the number of typed characters. + */ + private static final int FULL_WORD_SCORE_MULTIPLIER = 2; // Bigram frequency is a fixed point number with 1 meaning 1.2 and 255 meaning 1.8. protected static final int BIGRAM_MAX_FREQUENCY = 255; @@ -69,7 +72,7 @@ public class ExpandableDictionary extends Dictionary { mData = new Node[INCREMENT]; } - void add(Node n) { + void add(final Node n) { if (mLength + 1 > mData.length) { Node[] tempData = new Node[mLength + INCREMENT]; if (mLength > 0) { @@ -172,7 +175,7 @@ public class ExpandableDictionary extends Dictionary { } } - public void setRequiresReload(boolean reload) { + public void setRequiresReload(final boolean reload) { synchronized (mUpdatingLock) { mRequiresReload = reload; } @@ -202,8 +205,8 @@ public class ExpandableDictionary extends Dictionary { addWordRec(mRoots, word, 0, shortcutTarget, frequency, null); } - private void addWordRec(NodeArray children, final String word, final int depth, - final String shortcutTarget, final int frequency, Node parentNode) { + private void addWordRec(final NodeArray children, final String word, final int depth, + final String shortcutTarget, final int frequency, final Node parentNode) { final int wordLength = word.length(); if (wordLength <= depth) return; final char c = word.charAt(depth); @@ -248,7 +251,7 @@ public class ExpandableDictionary extends Dictionary { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { if (reloadDictionaryIfRequired()) return null; if (composer.size() > 1) { if (composer.size() >= BinaryDictionary.MAX_WORD_LENGTH) { @@ -267,8 +270,7 @@ public class ExpandableDictionary extends Dictionary { // This reloads the dictionary if required, and returns whether it's currently updating its // contents or not. - // @VisibleForTesting - boolean reloadDictionaryIfRequired() { + private boolean reloadDictionaryIfRequired() { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -277,7 +279,7 @@ public class ExpandableDictionary extends Dictionary { } protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); mInputLength = codes.size(); if (mCodes.length < mInputLength) mCodes = new int[mInputLength][]; @@ -305,7 +307,7 @@ public class ExpandableDictionary extends Dictionary { } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { synchronized (mUpdatingLock) { // If we need to update, start off a background task if (mRequiresReload) startDictionaryLoadingTaskLocked(); @@ -320,7 +322,7 @@ public class ExpandableDictionary extends Dictionary { return (node == null) ? false : !node.mShortcutOnly; } - protected boolean removeBigram(String word1, String word2) { + protected boolean removeBigram(final String word1, final String word2) { // Refer to addOrSetBigram() about word1.toLowerCase() final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); final Node secondWord = searchWord(mRoots, word2, 0, null); @@ -345,13 +347,13 @@ public class ExpandableDictionary extends Dictionary { /** * Returns the word's frequency or -1 if not found */ - protected int getWordFrequency(CharSequence word) { + protected int getWordFrequency(final String word) { // Case-sensitive search final Node node = searchNode(mRoots, word, 0, word.length()); return (node == null) ? -1 : node.mFrequency; } - protected NextWord getBigramWord(String word1, String word2) { + protected NextWord getBigramWord(final String word1, final String word2) { // Refer to addOrSetBigram() about word1.toLowerCase() final Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null); final Node secondWord = searchWord(mRoots, word2, 0, null); @@ -368,7 +370,8 @@ public class ExpandableDictionary extends Dictionary { return null; } - private static int computeSkippedWordFinalFreq(int freq, int snr, int inputLength) { + private static int computeSkippedWordFinalFreq(final int freq, final int snr, + final 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) { @@ -431,9 +434,9 @@ public class ExpandableDictionary extends Dictionary { * @param suggestions the list in which to add suggestions */ // TODO: Share this routine with the native code for BinaryDictionary - protected void getWordsRec(NodeArray roots, final WordComposer codes, final char[] word, - final int depth, final boolean completion, int snr, int inputIndex, int skipPos, - final ArrayList<SuggestedWordInfo> suggestions) { + protected void getWordsRec(final NodeArray roots, final WordComposer codes, final char[] word, + final int depth, final boolean completion, final int snr, final int inputIndex, + final int skipPos, final ArrayList<SuggestedWordInfo> suggestions) { final int count = roots.mLength; final int codeSize = mInputLength; // Optimization: Prune out words that are too long compared to how much was typed. @@ -472,8 +475,8 @@ public class ExpandableDictionary extends Dictionary { getWordsRec(children, codes, word, depth + 1, true, snr, inputIndex, skipPos, suggestions); } - } else if ((c == Keyboard.CODE_SINGLE_QUOTE - && currentChars[0] != Keyboard.CODE_SINGLE_QUOTE) || depth == skipPos) { + } else if ((c == Constants.CODE_SINGLE_QUOTE + && currentChars[0] != Constants.CODE_SINGLE_QUOTE) || depth == skipPos) { // Skip the ' and continue deeper word[depth] = c; if (children != null) { @@ -524,11 +527,13 @@ public class ExpandableDictionary extends Dictionary { } } - public int setBigramAndGetFrequency(String word1, String word2, int frequency) { + public int setBigramAndGetFrequency(final String word1, final String word2, + final int frequency) { return setBigramAndGetFrequency(word1, word2, frequency, null /* unused */); } - public int setBigramAndGetFrequency(String word1, String word2, ForgettingCurveParams fcp) { + public int setBigramAndGetFrequency(final String word1, final String word2, + final ForgettingCurveParams fcp) { return setBigramAndGetFrequency(word1, word2, 0 /* unused */, fcp); } @@ -540,8 +545,8 @@ public class ExpandableDictionary extends Dictionary { * @param fcp an instance of ForgettingCurveParams to use for decay policy * @return returns the final bigram frequency */ - private int setBigramAndGetFrequency( - String word1, String word2, int frequency, ForgettingCurveParams fcp) { + private int setBigramAndGetFrequency(final String word1, final String word2, + final int frequency, final ForgettingCurveParams fcp) { // We don't want results to be different according to case of the looked up left hand side // word. We do want however to return the correct case for the right hand side. // So we want to squash the case of the left hand side, and preserve that of the right @@ -572,7 +577,8 @@ public class ExpandableDictionary extends Dictionary { * Searches for the word and add the word if it does not exist. * @return Returns the terminal node of the word we are searching for. */ - private Node searchWord(NodeArray children, String word, int depth, Node parentNode) { + private Node searchWord(final NodeArray children, final String word, final int depth, + final Node parentNode) { final int wordLength = word.length(); final char c = word.charAt(depth); // Does children have the current character? @@ -602,36 +608,17 @@ public class ExpandableDictionary extends Dictionary { return searchWord(childNode.mChildren, word, depth + 1, childNode); } - private void runBigramReverseLookUp(final CharSequence previousWord, + private void runBigramReverseLookUp(final String previousWord, final ArrayList<SuggestedWordInfo> suggestions) { // Search for the lowercase version of the word only, because that's where bigrams // store their sons. - Node prevWord = searchNode(mRoots, previousWord.toString().toLowerCase(), 0, + final Node prevWord = searchNode(mRoots, previousWord.toLowerCase(), 0, previousWord.length()); if (prevWord != null && prevWord.mNGrams != null) { reverseLookUp(prevWord.mNGrams, suggestions); } } - /** - * Used for testing purposes and in the spell checker - * This function will wait for loading from database to be done - */ - void waitForDictionaryLoading() { - while (mUpdatingDictionary) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - // - } - } - } - - protected final void blockingReloadDictionaryIfRequired() { - reloadDictionaryIfRequired(); - waitForDictionaryLoading(); - } - // Local to reverseLookUp, but do not allocate each time. private final char[] mLookedUpString = new char[BinaryDictionary.MAX_WORD_LENGTH]; @@ -641,7 +628,7 @@ public class ExpandableDictionary extends Dictionary { * @param terminalNodes list of terminal nodes we want to add * @param suggestions the suggestion collection to add the word to */ - private void reverseLookUp(LinkedList<NextWord> terminalNodes, + private void reverseLookUp(final LinkedList<NextWord> terminalNodes, final ArrayList<SuggestedWordInfo> suggestions) { Node node; int freq; @@ -714,7 +701,7 @@ public class ExpandableDictionary extends Dictionary { } } - private static char toLowerCase(char c) { + private static char toLowerCase(final char c) { char baseChar = c; if (c < BASE_CHARS.length) { baseChar = BASE_CHARS[c]; diff --git a/java/src/com/android/inputmethod/latin/ImfUtils.java b/java/src/com/android/inputmethod/latin/ImfUtils.java deleted file mode 100644 index 2674e4575..000000000 --- a/java/src/com/android/inputmethod/latin/ImfUtils.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; - -import android.content.Context; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; - -import java.util.Collections; -import java.util.List; - -/** - * Utility class for Input Method Framework - */ -public final class ImfUtils { - private ImfUtils() { - // This utility class is not publicly instantiable. - } - - private static InputMethodManager sInputMethodManager; - - public static InputMethodManager getInputMethodManager(Context context) { - if (sInputMethodManager == null) { - sInputMethodManager = (InputMethodManager)context.getSystemService( - Context.INPUT_METHOD_SERVICE); - } - return sInputMethodManager; - } - - private static InputMethodInfo sInputMethodInfoOfThisIme; - - public static InputMethodInfo getInputMethodInfoOfThisIme(Context context) { - if (sInputMethodInfoOfThisIme == null) { - final InputMethodManager imm = getInputMethodManager(context); - final String packageName = context.getPackageName(); - for (final InputMethodInfo imi : imm.getInputMethodList()) { - if (imi.getPackageName().equals(packageName)) - return imi; - } - throw new RuntimeException("Can not find input method id for " + packageName); - } - return sInputMethodInfoOfThisIme; - } - - public static String getInputMethodIdOfThisIme(Context context) { - return getInputMethodInfoOfThisIme(context).getId(); - } - - public static boolean checkIfSubtypeBelongsToThisImeAndEnabled(Context context, - InputMethodSubtype ims) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final InputMethodManager imm = getInputMethodManager(context); - // TODO: Cache all subtypes of this IME for optimization - final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(myImi, true); - for (final InputMethodSubtype subtype : subtypes) { - if (subtype.equals(ims)) { - return true; - } - } - return false; - } - - public static boolean checkIfSubtypeBelongsToThisIme(Context context, - InputMethodSubtype ims) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final int count = myImi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = myImi.getSubtypeAt(i); - if (subtype.equals(ims)) { - return true; - } - } - return false; - } - - public static InputMethodSubtype getCurrentInputMethodSubtype(Context context, - InputMethodSubtype defaultSubtype) { - final InputMethodManager imm = getInputMethodManager(context); - final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); - return (currentSubtype != null) ? currentSubtype : defaultSubtype; - } - - public static boolean hasMultipleEnabledIMEsOrSubtypes(Context context, - final boolean shouldIncludeAuxiliarySubtypes) { - final InputMethodManager imm = getInputMethodManager(context); - final List<InputMethodInfo> enabledImis = imm.getEnabledInputMethodList(); - return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, enabledImis); - } - - public static boolean hasMultipleEnabledSubtypesInThisIme(Context context, - final boolean shouldIncludeAuxiliarySubtypes) { - final InputMethodInfo myImi = getInputMethodInfoOfThisIme(context); - final List<InputMethodInfo> imiList = Collections.singletonList(myImi); - return hasMultipleEnabledSubtypes(context, shouldIncludeAuxiliarySubtypes, imiList); - } - - private static boolean hasMultipleEnabledSubtypes(Context context, - final boolean shouldIncludeAuxiliarySubtypes, List<InputMethodInfo> imiList) { - final InputMethodManager imm = getInputMethodManager(context); - - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : imiList) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List<InputMethodSubtype> subtypes = - imm.getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - continue; - } - } - - if (filteredImisCount > 1) { - return true; - } - final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(null, true); - int keyboardCount = 0; - // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's - // both explicitly and implicitly enabled input method subtype. - // (The current IME should be LatinIME.) - for (InputMethodSubtype subtype : subtypes) { - if (KEYBOARD_MODE.equals(subtype.getMode())) { - ++keyboardCount; - } - } - return keyboardCount > 1; - } - - public static InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet( - Context context, String localeString, String keyboardLayoutSetName) { - final InputMethodInfo imi = getInputMethodInfoOfThisIme(context); - final int count = imi.getSubtypeCount(); - for (int i = 0; i < count; i++) { - final InputMethodSubtype subtype = imi.getSubtypeAt(i); - final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype); - if (localeString.equals(subtype.getLocale()) - && keyboardLayoutSetName.equals(layoutName)) { - return subtype; - } - } - return null; - } - - public static void setAdditionalInputMethodSubtypes(Context context, - InputMethodSubtype[] subtypes) { - final InputMethodManager imm = getInputMethodManager(context); - final String imiId = getInputMethodIdOfThisIme(context); - imm.setAdditionalInputMethodSubtypes(imiId, subtypes); - } -} diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 422fc72d3..1c5bff383 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -117,36 +117,56 @@ public final class InputAttributes { if (inputClass == InputType.TYPE_CLASS_DATETIME) Log.i(TAG, " TYPE_CLASS_DATETIME"); Log.i(TAG, "Variation:"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT)) - Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_FILTER)) - Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE)) - Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_NORMAL)) - Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC)) - Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)) - Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_URI)) - Log.i(TAG, " TYPE_TEXT_VARIATION_URI"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); - if (0 != (inputType & InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD)) - Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD"); + switch (InputType.TYPE_MASK_VARIATION & inputType) { + case InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT: + Log.i(TAG, " TYPE_TEXT_VARIATION_EMAIL_SUBJECT"); + break; + case InputType.TYPE_TEXT_VARIATION_FILTER: + Log.i(TAG, " TYPE_TEXT_VARIATION_FILTER"); + break; + case InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE: + Log.i(TAG, " TYPE_TEXT_VARIATION_LONG_MESSAGE"); + break; + case InputType.TYPE_TEXT_VARIATION_NORMAL: + Log.i(TAG, " TYPE_TEXT_VARIATION_NORMAL"); + break; + case InputType.TYPE_TEXT_VARIATION_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_PASSWORD"); + break; + case InputType.TYPE_TEXT_VARIATION_PERSON_NAME: + Log.i(TAG, " TYPE_TEXT_VARIATION_PERSON_NAME"); + break; + case InputType.TYPE_TEXT_VARIATION_PHONETIC: + Log.i(TAG, " TYPE_TEXT_VARIATION_PHONETIC"); + break; + case InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_POSTAL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE: + Log.i(TAG, " TYPE_TEXT_VARIATION_SHORT_MESSAGE"); + break; + case InputType.TYPE_TEXT_VARIATION_URI: + Log.i(TAG, " TYPE_TEXT_VARIATION_URI"); + break; + case InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_VISIBLE_PASSWORD"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EDIT_TEXT"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS"); + break; + case InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD: + Log.i(TAG, " TYPE_TEXT_VARIATION_WEB_PASSWORD"); + break; + default: + Log.i(TAG, " Unknown variation"); + break; + } Log.i(TAG, "Flags:"); if (0 != (inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)) Log.i(TAG, " TYPE_TEXT_FLAG_NO_SUGGESTIONS"); diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java index 6b48aabb3..7dffd96dd 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.annotations.UsedForTesting; + // TODO: This class is not thread-safe. public final class InputPointers { private final int mDefaultCapacity; @@ -39,7 +41,8 @@ public final class InputPointers { mTimes.add(index, time); } - public void addPointer(int x, int y, int pointerId, int time) { + @UsedForTesting + void addPointer(int x, int y, int pointerId, int time) { mXCoordinates.add(x); mYCoordinates.add(y); mPointerIds.add(pointerId); @@ -66,7 +69,8 @@ public final class InputPointers { * @param startPos the starting index of the pointers in {@code src}. * @param length the number of pointers to be appended. */ - public void append(InputPointers src, int startPos, int length) { + @UsedForTesting + void append(InputPointers src, int startPos, int length) { if (length == 0) { return; } diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 94cdc9b85..44ef01204 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -44,7 +44,7 @@ public final class LastComposedWord { public final String mTypedWord; public final String mCommittedWord; public final String mSeparatorString; - public final CharSequence mPrevWord; + public final String mPrevWord; public final InputPointers mInputPointers = new InputPointers(BinaryDictionary.MAX_WORD_LENGTH); private boolean mActive; @@ -56,7 +56,7 @@ public final class LastComposedWord { // immutable. Do not fiddle with their contents after you passed them to this constructor. public LastComposedWord(final int[] primaryKeyCodes, final InputPointers inputPointers, final String typedWord, final String committedWord, - final String separatorString, final CharSequence prevWord) { + final String separatorString, final String prevWord) { mPrimaryKeyCodes = primaryKeyCodes; if (inputPointers != null) { mInputPointers.copy(inputPointers); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index f416396e8..e3508ac44 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -60,8 +60,8 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.compat.CompatUtils; -import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatUtils; import com.android.inputmethod.compat.SuggestionSpanUtils; import com.android.inputmethod.keyboard.KeyDetector; @@ -132,14 +132,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private View mKeyPreviewBackingView; private View mSuggestionsContainer; private SuggestionStripView mSuggestionStripView; - /* package for tests */ Suggest mSuggest; + @UsedForTesting Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; private ApplicationInfo mTargetApplicationInfo; - private InputMethodManagerCompatWrapper mImm; + private RichInputMethodManager mRichImm; private Resources mResources; private SharedPreferences mPrefs; - /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher; + @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeSwitcher mSubtypeSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); @@ -163,17 +163,17 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private int mDeleteCount; private long mLastKeyTime; - private AudioAndHapticFeedbackManager mFeedbackManager; - // Member variables for remembering the current device orientation. private int mDisplayOrientation; // Object for reacting to adding/removing a dictionary pack. + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. private BroadcastReceiver mDictionaryPackInstallReceiver = - new DictionaryPackInstallBroadcastReceiver(this); + ProductionFlag.IS_EXPERIMENTAL + ? null : new DictionaryPackInstallBroadcastReceiver(this); // Keeps track of most recently inserted text (multi-character key) for reverting - private CharSequence mEnteredText; + private String mEnteredText; private boolean mIsAutoCorrectionIndicatorOn; @@ -373,9 +373,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mCurrentSubtypeUsed = true; } - public void switchSubtype(final IBinder token, final InputMethodManagerCompatWrapper imm, - final Context context) { - final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); + public void switchSubtype(final IBinder token, final RichInputMethodManager richImm) { + final InputMethodSubtype currentSubtype = richImm.getInputMethodManager() + .getCurrentInputMethodSubtype(); final InputMethodSubtype lastActiveSubtype = mLastActiveSubtype; final boolean currentSubtypeUsed = mCurrentSubtypeUsed; if (currentSubtypeUsed) { @@ -383,13 +383,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mCurrentSubtypeUsed = false; } if (currentSubtypeUsed - && ImfUtils.checkIfSubtypeBelongsToThisImeAndEnabled(context, lastActiveSubtype) + && richImm.checkIfSubtypeBelongsToThisImeAndEnabled(lastActiveSubtype) && !currentSubtype.equals(lastActiveSubtype)) { - final String id = ImfUtils.getInputMethodIdOfThisIme(context); - imm.setInputMethodAndSubtype(token, id, lastActiveSubtype); + richImm.setInputMethodAndSubtype(token, lastActiveSubtype); return; } - imm.switchToNextInputMethod(token, true /* onlyCurrentIme */); + richImm.switchToNextInputMethod(token, true /* onlyCurrentIme */); } } @@ -410,14 +409,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.getInstance().init(this, prefs); } - InputMethodManagerCompatWrapper.init(this); + RichInputMethodManager.init(this); SubtypeSwitcher.init(this); KeyboardSwitcher.init(this, prefs); AccessibilityUtils.init(this); super.onCreate(); - mImm = InputMethodManagerCompatWrapper.getInstance(); + mRichImm = RichInputMethodManager.getInstance(); mHandler.onCreate(); DEBUG = LatinImeLogger.sDBG; @@ -426,7 +425,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction loadSettings(); - ImfUtils.setAdditionalInputMethodSubtypes(this, mCurrentSettings.getAdditionalSubtypes()); + mRichImm.setAdditionalInputMethodSubtypes(mCurrentSettings.getAdditionalSubtypes()); initSuggest(); @@ -439,20 +438,23 @@ public final class LatinIME extends InputMethodService implements KeyboardAction filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION); registerReceiver(mReceiver, filter); - final IntentFilter packageFilter = new IntentFilter(); - packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - packageFilter.addDataScheme(SCHEME_PACKAGE); - registerReceiver(mDictionaryPackInstallReceiver, packageFilter); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. + if (!ProductionFlag.IS_EXPERIMENTAL) { + final IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addDataScheme(SCHEME_PACKAGE); + registerReceiver(mDictionaryPackInstallReceiver, packageFilter); - final IntentFilter newDictFilter = new IntentFilter(); - newDictFilter.addAction( - DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); - registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); + final IntentFilter newDictFilter = new IntentFilter(); + newDictFilter.addAction( + DictionaryPackInstallBroadcastReceiver.NEW_DICTIONARY_INTENT_ACTION); + registerReceiver(mDictionaryPackInstallReceiver, newDictFilter); + } } // Has to be package-visible for unit tests - /* package for test */ + @UsedForTesting void loadSettings() { // Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged() // is not guaranteed. It may even be called at the same time on a different thread. @@ -466,7 +468,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } }; mCurrentSettings = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale()); - mFeedbackManager = new AudioAndHapticFeedbackManager(this, mCurrentSettings); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } @@ -569,7 +570,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mSuggest = null; } unregisterReceiver(mReceiver); - unregisterReceiver(mDictionaryPackInstallReceiver); + // TODO: The experimental version is not supported by the Dictionary Pack Service yet. + if (!ProductionFlag.IS_EXPERIMENTAL) { + unregisterReceiver(mDictionaryPackInstallReceiver); + } LatinImeLogger.commit(); LatinImeLogger.onDestroy(); super.onDestroy(); @@ -578,7 +582,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onConfigurationChanged(final Configuration conf) { // System locale has been changed. Needs to reload keyboard. - if (mSubtypeSwitcher.onConfigurationChanged(conf, this)) { + if (mSubtypeSwitcher.onConfigurationChanged(conf)) { loadKeyboard(); } // If orientation changed while predicting, commit the change @@ -718,8 +722,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction .updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled(); if (!currentSubtypeEnabled) { // Current subtype is disabled. Needs to update subtype and keyboard. - final InputMethodSubtype newSubtype = ImfUtils.getCurrentInputMethodSubtype( - this, mSubtypeSwitcher.getNoLanguageSubtype()); + final InputMethodSubtype newSubtype = mRichImm.getCurrentInputMethodSubtype( + mSubtypeSwitcher.getNoLanguageSubtype()); mSubtypeSwitcher.updateSubtype(newSubtype); loadKeyboard(); } @@ -1129,7 +1133,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void commitTyped(final String separatorString) { if (!mWordComposer.isComposingWord()) return; - final CharSequence typedWord = mWordComposer.getTypedWord(); + final String typedWord = mWordComposer.getTypedWord(); if (typedWord.length() > 0) { commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString); @@ -1166,7 +1170,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction CharSequence lastTwo = mConnection.getTextBeforeCursor(2, 0); // It is guaranteed lastTwo.charAt(1) is a swapper - else this method is not called. if (lastTwo != null && lastTwo.length() == 2 - && lastTwo.charAt(0) == Keyboard.CODE_SPACE) { + && lastTwo.charAt(0) == Constants.CODE_SPACE) { mConnection.deleteSurroundingText(2, 0); mConnection.commitText(lastTwo.charAt(1) + " ", 1); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -1182,8 +1186,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction final CharSequence lastThree = mConnection.getTextBeforeCursor(3, 0); if (lastThree != null && lastThree.length() == 3 && canBeFollowedByPeriod(lastThree.charAt(0)) - && lastThree.charAt(1) == Keyboard.CODE_SPACE - && lastThree.charAt(2) == Keyboard.CODE_SPACE) { + && lastThree.charAt(1) == Constants.CODE_SPACE + && lastThree.charAt(2) == Constants.CODE_SPACE) { mHandler.cancelDoubleSpacesTimer(); mConnection.deleteSurroundingText(2, 0); mConnection.commitText(". ", 1); @@ -1197,12 +1201,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: Check again whether there really ain't a better way to check this. // TODO: This should probably be language-dependant... return Character.isLetterOrDigit(codePoint) - || codePoint == Keyboard.CODE_SINGLE_QUOTE - || codePoint == Keyboard.CODE_DOUBLE_QUOTE - || codePoint == Keyboard.CODE_CLOSING_PARENTHESIS - || codePoint == Keyboard.CODE_CLOSING_SQUARE_BRACKET - || codePoint == Keyboard.CODE_CLOSING_CURLY_BRACKET - || codePoint == Keyboard.CODE_CLOSING_ANGLE_BRACKET; + || codePoint == Constants.CODE_SINGLE_QUOTE + || codePoint == Constants.CODE_DOUBLE_QUOTE + || codePoint == Constants.CODE_CLOSING_PARENTHESIS + || codePoint == Constants.CODE_CLOSING_SQUARE_BRACKET + || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET + || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET; } // Callback for the {@link SuggestionStripView}, to call when the "add to dictionary" hint is @@ -1230,9 +1234,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (isShowingOptionDialog()) return false; switch (requestCode) { case CODE_SHOW_INPUT_METHOD_PICKER: - if (ImfUtils.hasMultipleEnabledIMEsOrSubtypes( - this, true /* include aux subtypes */)) { - mImm.showInputMethodPicker(); + if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) { + mRichImm.getInputMethodManager().showInputMethodPicker(); return true; } return false; @@ -1256,10 +1259,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void handleLanguageSwitchKey() { final IBinder token = getWindow().getWindow().getAttributes().token; if (mCurrentSettings.mIncludesOtherImesInLanguageSwitchList) { - mImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); + mRichImm.switchToNextInputMethod(token, false /* onlyCurrentIme */); return; } - mSubtypeState.switchSubtype(token, mImm, this); + mSubtypeState.switchSubtype(token, mRichImm); } private void sendDownUpKeyEventForBackwardCompatibility(final int code) { @@ -1285,7 +1288,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // 16 is android.os.Build.VERSION_CODES.JELLY_BEAN but we can't write it because // we want to be able to compile against the Ice Cream Sandwich SDK. - if (Keyboard.CODE_ENTER == code && mTargetApplicationInfo != null + if (Constants.CODE_ENTER == code && mTargetApplicationInfo != null && mTargetApplicationInfo.targetSdkVersion < 16) { // Backward compatibility mode. Before Jelly bean, the keyboard would simulate // a hardware keyboard event on pressing enter or delete. This is bad for many @@ -1302,7 +1305,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction @Override public void onCodeInput(final int primaryCode, final int x, final int y) { final long when = SystemClock.uptimeMillis(); - if (primaryCode != Keyboard.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { + if (primaryCode != Constants.CODE_DELETE || when > mLastKeyTime + QUICK_PRESS) { mDeleteCount = 0; } mLastKeyTime = when; @@ -1317,42 +1320,42 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (!mWordComposer.isComposingWord()) mIsAutoCorrectionIndicatorOn = false; // TODO: Consolidate the double space timer, mLastKeyTime, and the space state. - if (primaryCode != Keyboard.CODE_SPACE) { + if (primaryCode != Constants.CODE_SPACE) { mHandler.cancelDoubleSpacesTimer(); } boolean didAutoCorrect = false; switch (primaryCode) { - case Keyboard.CODE_DELETE: + case Constants.CODE_DELETE: mSpaceState = SPACE_STATE_NONE; handleBackspace(spaceState); mDeleteCount++; mExpectingUpdateSelection = true; LatinImeLogger.logOnDelete(x, y); break; - case Keyboard.CODE_SHIFT: - case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: + case Constants.CODE_SHIFT: + case Constants.CODE_SWITCH_ALPHA_SYMBOL: // Shift and symbol key is handled in onPressKey() and onReleaseKey(). break; - case Keyboard.CODE_SETTINGS: + case Constants.CODE_SETTINGS: onSettingsKeyPressed(); break; - case Keyboard.CODE_SHORTCUT: + case Constants.CODE_SHORTCUT: mSubtypeSwitcher.switchToShortcutIME(this); break; - case Keyboard.CODE_ACTION_ENTER: + case Constants.CODE_ACTION_ENTER: performEditorAction(getActionId(switcher.getKeyboard())); break; - case Keyboard.CODE_ACTION_NEXT: + case Constants.CODE_ACTION_NEXT: performEditorAction(EditorInfo.IME_ACTION_NEXT); break; - case Keyboard.CODE_ACTION_PREVIOUS: + case Constants.CODE_ACTION_PREVIOUS: performEditorAction(EditorInfo.IME_ACTION_PREVIOUS); break; - case Keyboard.CODE_LANGUAGE_SWITCH: + case Constants.CODE_LANGUAGE_SWITCH: handleLanguageSwitchKey(); break; - case Keyboard.CODE_RESEARCH: + case Constants.CODE_RESEARCH: if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.getInstance().onResearchKeySelected(this); } @@ -1387,10 +1390,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } switcher.onCodeInput(primaryCode); // Reset after any single keystroke, except shift and symbol-shift - if (!didAutoCorrect && primaryCode != Keyboard.CODE_SHIFT - && primaryCode != Keyboard.CODE_SWITCH_ALPHA_SYMBOL) + if (!didAutoCorrect && primaryCode != Constants.CODE_SHIFT + && primaryCode != Constants.CODE_SWITCH_ALPHA_SYMBOL) mLastComposedWord.deactivate(); - if (Keyboard.CODE_DELETE != primaryCode) { + if (Constants.CODE_DELETE != primaryCode) { mEnteredText = null; } mConnection.endBatchEdit(); @@ -1401,7 +1404,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Called from PointerTracker through the KeyboardActionListener interface @Override - public void onTextInput(final CharSequence rawText) { + public void onTextInput(final String rawText) { mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { commitCurrentAutoCorrection(rawText.toString()); @@ -1409,7 +1412,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction resetComposingState(true /* alsoResetLastComposedWord */); } mHandler.postUpdateSuggestionStrip(); - final CharSequence text = specificTldProcessingOnTextInput(rawText); + final String text = specificTldProcessingOnTextInput(rawText); if (SPACE_STATE_PHANTOM == mSpaceState) { promotePhantomSpace(); } @@ -1418,7 +1421,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Space state must be updated before calling updateShiftState mSpaceState = SPACE_STATE_NONE; mKeyboardSwitcher.updateShiftState(); - mKeyboardSwitcher.onCodeInput(Keyboard.CODE_OUTPUT_TEXT); + mKeyboardSwitcher.onCodeInput(Constants.CODE_OUTPUT_TEXT); mEnteredText = text; } @@ -1547,8 +1550,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void showGesturePreviewAndSuggestionStrip(final SuggestedWords suggestedWords, final boolean dismissGestureFloatingPreviewText) { - final String batchInputText = (suggestedWords.size() > 0) - ? suggestedWords.getWord(0) : null; + final String batchInputText = suggestedWords.isEmpty() + ? null : suggestedWords.getWord(0); final KeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); mainKeyboardView.showGestureFloatingPreviewText(batchInputText); showSuggestionStrip(suggestedWords, null); @@ -1566,8 +1569,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction public void onEndBatchInput(final InputPointers batchPointers) { final SuggestedWords suggestedWords = BatchInputUpdater.getInstance().onEndBatchInput( batchPointers, this); - final String batchInputText = (suggestedWords.size() > 0) - ? suggestedWords.getWord(0) : null; + final String batchInputText = suggestedWords.isEmpty() + ? null : suggestedWords.getWord(0); if (TextUtils.isEmpty(batchInputText)) { return; } @@ -1584,8 +1587,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mKeyboardSwitcher.updateShiftState(); } - private CharSequence specificTldProcessingOnTextInput(final CharSequence text) { - if (text.length() <= 1 || text.charAt(0) != Keyboard.CODE_PERIOD + private String specificTldProcessingOnTextInput(final String text) { + if (text.length() <= 1 || text.charAt(0) != Constants.CODE_PERIOD || !Character.isLetter(text.charAt(1))) { // Not a tld: do nothing. return text; @@ -1596,8 +1599,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: use getCodePointBeforeCursor instead to improve performance and simplify the code final CharSequence lastOne = mConnection.getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == Keyboard.CODE_PERIOD) { - return text.subSequence(1, text.length()); + && lastOne.charAt(0) == Constants.CODE_PERIOD) { + return text.substring(1); } else { return text; } @@ -1619,8 +1622,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mWordComposer.isComposingWord()) { final int length = mWordComposer.size(); if (length > 0) { - // Immediately after a batch input. - if (SPACE_STATE_PHANTOM == spaceState) { + if (mWordComposer.isBatchMode()) { mWordComposer.reset(); } else { mWordComposer.deleteLast(); @@ -1701,7 +1703,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean maybeStripSpace(final int code, final int spaceState, final boolean isFromSuggestionStrip) { - if (Keyboard.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { + if (Constants.CODE_ENTER == code && SPACE_STATE_SWAP_PUNCTUATION == spaceState) { mConnection.removeTrailingSpace(); return false; } else if ((SPACE_STATE_WEAK == spaceState @@ -1744,7 +1746,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // the character is a single quote. The idea here is, single quote is not a // separator and it should be treated as a normal character, except in the first // position where it should not start composing a word. - isComposingWord = (Keyboard.CODE_SINGLE_QUOTE != primaryCode); + isComposingWord = (Constants.CODE_SINGLE_QUOTE != primaryCode); // Here we don't need to reset the last composed word. It will be reset // when we commit this one, if we ever do; if on the other hand we backspace // it entirely and resume suggestions on the previous word, we'd like to still @@ -1753,15 +1755,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } if (isComposingWord) { final int keyX, keyY; - if (KeyboardActionListener.Adapter.isInvalidCoordinate(x) - || KeyboardActionListener.Adapter.isInvalidCoordinate(y)) { - keyX = x; - keyY = y; - } else { + if (Constants.isValidCoordinate(x) && Constants.isValidCoordinate(y)) { final KeyDetector keyDetector = mKeyboardSwitcher.getMainKeyboardView().getKeyDetector(); keyX = keyDetector.getTouchX(x); keyY = keyDetector.getTouchY(y); + } else { + keyX = x; + keyY = y; } mWordComposer.add(primaryCode, keyX, keyY); // If it's the first letter, make note of auto-caps state @@ -1812,7 +1813,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } sendKeyCodePoint(primaryCode); - if (Keyboard.CODE_SPACE == primaryCode) { + if (Constants.CODE_SPACE == primaryCode) { if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (maybeDoubleSpace()) { mSpaceState = SPACE_STATE_DOUBLE; @@ -1857,7 +1858,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return didAutoCorrect; } - private CharSequence getTextWithUnderline(final CharSequence text) { + private CharSequence getTextWithUnderline(final String text) { return mIsAutoCorrectionIndicatorOn ? SuggestionSpanUtils.getTextWithAutoCorrectionIndicatorUnderline(this, text) : text; @@ -1874,7 +1875,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: make this private // Outside LatinIME, only used by the test suite. - /* package for tests */ + @UsedForTesting boolean isShowingPunctuationList() { if (mSuggestionStripView == null) return false; return mCurrentSettings.mSuggestPuncList == mSuggestionStripView.getSuggestions(); @@ -1944,7 +1945,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private SuggestedWords getSuggestedWords(final int sessionId) { final Keyboard keyboard = mKeyboardSwitcher.getKeyboard(); - if (keyboard == null) { + if (keyboard == null || mSuggest == null) { return SuggestedWords.EMPTY; } final String typedWord = mWordComposer.getTypedWord(); @@ -1952,7 +1953,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we // should just skip whitespace if any, so 1. // TODO: this is slow (2-way IPC) - we should probably cache this instead. - final CharSequence prevWord = + final String prevWord = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, mWordComposer.isComposingWord() ? 2 : 1); final SuggestedWords suggestedWords = mSuggest.getSuggestedWords(mWordComposer, @@ -1961,7 +1962,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction return maybeRetrieveOlderSuggestions(typedWord, suggestedWords); } - private SuggestedWords maybeRetrieveOlderSuggestions(final CharSequence typedWord, + private SuggestedWords maybeRetrieveOlderSuggestions(final String typedWord, final SuggestedWords suggestedWords) { // TODO: consolidate this into getSuggestedWords // We update the suggestion strip only when we have some suggestions to show, i.e. when @@ -1991,21 +1992,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } } - private void showSuggestionStrip(final SuggestedWords suggestedWords, - final CharSequence typedWord) { - if (null == suggestedWords || suggestedWords.size() <= 0) { + private void showSuggestionStrip(final SuggestedWords suggestedWords, final String typedWord) { + if (suggestedWords.isEmpty()) { clearSuggestionStrip(); return; } - final CharSequence autoCorrection; - if (suggestedWords.size() > 0) { - if (suggestedWords.mWillAutoCorrect) { - autoCorrection = suggestedWords.getWord(1); - } else { - autoCorrection = typedWord; - } + final String autoCorrection; + if (suggestedWords.mWillAutoCorrect) { + autoCorrection = suggestedWords.getWord(1); } else { - autoCorrection = null; + autoCorrection = typedWord; } mWordComposer.setAutoCorrection(autoCorrection); final boolean isAutoCorrection = suggestedWords.willAutoCorrect(); @@ -2019,9 +2015,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mHandler.hasPendingUpdateSuggestions()) { updateSuggestionStrip(); } - final CharSequence typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); + final String typedAutoCorrection = mWordComposer.getAutoCorrectionOrNull(); final String typedWord = mWordComposer.getTypedWord(); - final CharSequence autoCorrection = (typedAutoCorrection != null) + final String autoCorrection = (typedAutoCorrection != null) ? typedAutoCorrection : typedWord; if (autoCorrection != null) { if (TextUtils.isEmpty(typedWord)) { @@ -2052,7 +2048,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Called from {@link SuggestionStripView} through the {@link SuggestionStripView#Listener} // interface @Override - public void pickSuggestionManually(final int index, final CharSequence suggestion) { + public void pickSuggestionManually(final int index, final String suggestion) { final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { @@ -2122,7 +2118,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction && !AutoCorrection.isValidWord(mSuggest.getUnigramDictionaries(), suggestion, true); if (ProductionFlag.IS_INTERNAL) { - Stats.onSeparator((char)Keyboard.CODE_SPACE, + Stats.onSeparator((char)Constants.CODE_SPACE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); } if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) { @@ -2137,13 +2133,13 @@ public final class LatinIME extends InputMethodService implements KeyboardAction /** * Commits the chosen word to the text field and saves it for later retrieval. */ - private void commitChosenWord(final CharSequence chosenWord, final int commitType, + private void commitChosenWord(final String chosenWord, final int commitType, final String separatorString) { final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); // Add the word to the user history dictionary - final CharSequence prevWord = addToUserHistoryDictionary(chosenWord); + final String prevWord = addToUserHistoryDictionary(chosenWord); // TODO: figure out here if this is an auto-correct or if the best word is actually // what user typed. Note: currently this is done much later in // LastComposedWord#didCommitTypedWord by string equality of the remembered @@ -2162,7 +2158,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction setSuggestionStripShown(isSuggestionsStripVisible()); } - private CharSequence addToUserHistoryDictionary(final CharSequence suggestion) { + private String addToUserHistoryDictionary(final String suggestion) { if (TextUtils.isEmpty(suggestion)) return null; if (mSuggest == null) return null; @@ -2177,19 +2173,18 @@ public final class LatinIME extends InputMethodService implements KeyboardAction = mConnection.getNthPreviousWord(mCurrentSettings.mWordSeparators, 2); final String secondWord; if (mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps()) { - secondWord = suggestion.toString().toLowerCase( - mSubtypeSwitcher.getCurrentSubtypeLocale()); + secondWord = suggestion.toLowerCase(mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { - secondWord = suggestion.toString(); + secondWord = suggestion; } // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid". // We don't add words with 0-frequency (assuming they would be profanity etc.). final int maxFreq = AutoCorrection.getMaxFrequency( mSuggest.getUnigramDictionaries(), suggestion); if (maxFreq == 0) return null; - userHistoryDictionary.addToUserHistory(null == prevWord ? null : prevWord.toString(), - secondWord, maxFreq > 0); - return prevWord; + final String prevWordString = (null == prevWord) ? null : prevWord.toString(); + userHistoryDictionary.addToUserHistory(prevWordString, secondWord, maxFreq > 0); + return prevWordString; } return null; } @@ -2214,9 +2209,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } private void revertCommit() { - final CharSequence previousWord = mLastComposedWord.mPrevWord; + final String previousWord = mLastComposedWord.mPrevWord; final String originallyTypedWord = mLastComposedWord.mTypedWord; - final CharSequence committedWord = mLastComposedWord.mCommittedWord; + final String committedWord = mLastComposedWord.mCommittedWord; final int cancelLength = committedWord.length(); final int separatorLength = LastComposedWord.getSeparatorLength( mLastComposedWord.mSeparatorString); @@ -2226,9 +2221,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mWordComposer.isComposingWord()) { throw new RuntimeException("revertCommit, but we are composing a word"); } - final String wordBeforeCursor = + final CharSequence wordBeforeCursor = mConnection.getTextBeforeCursor(deleteLength, 0) - .subSequence(0, cancelLength).toString(); + .subSequence(0, cancelLength); if (!TextUtils.equals(committedWord, wordBeforeCursor)) { throw new RuntimeException("revertCommit check failed: we thought we were " + "reverting \"" + committedWord @@ -2258,7 +2253,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // This essentially inserts a space, and that's it. public void promotePhantomSpace() { if (mCurrentSettings.shouldInsertSpacesAutomatically()) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } } @@ -2269,7 +2264,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // TODO: Make this private // Outside LatinIME, only used by the {@link InputTestsBase} test suite. - /* package for test */ + @UsedForTesting void loadKeyboard() { // When the device locale is changed in SetupWizard etc., this method may get called via // onConfigurationChanged before SoftInputWindow is shown. @@ -2285,13 +2280,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mHandler.postUpdateSuggestionStrip(); } - // TODO: Remove this method from {@link LatinIME} and move {@link FeedbackManager} to - // {@link KeyboardSwitcher}. Called from KeyboardSwitcher - public void hapticAndAudioFeedback(final int primaryCode) { - mFeedbackManager.hapticAndAudioFeedback( - primaryCode, mKeyboardSwitcher.getMainKeyboardView()); - } - // Callback called by PointerTracker through the KeyboardActionListener. This is called when a // key is depressed; release matching call is onReleaseKey below. @Override @@ -2308,16 +2296,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // If accessibility is on, ensure the user receives keyboard state updates. if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { switch (primaryCode) { - case Keyboard.CODE_SHIFT: + case Constants.CODE_SHIFT: AccessibleKeyboardViewProxy.getInstance().notifyShiftState(); break; - case Keyboard.CODE_SWITCH_ALPHA_SYMBOL: + case Constants.CODE_SWITCH_ALPHA_SYMBOL: AccessibleKeyboardViewProxy.getInstance().notifySymbolsState(); break; } } - if (Keyboard.CODE_DELETE == primaryCode) { + if (Constants.CODE_DELETE == primaryCode) { // This is a stopgap solution to avoid leaving a high surrogate alone in a text view. // In the future, we need to deprecate deteleSurroundingText() and have a surrogate // pair-friendly way of deleting characters in InputConnection. @@ -2337,7 +2325,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { mSubtypeSwitcher.onNetworkStateChanged(intent); } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) { - mFeedbackManager.onRingerModeChanged(); + mKeyboardSwitcher.onRingerModeChanged(); } } }; @@ -2374,7 +2362,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction getString(R.string.language_selection_title), getString(R.string.english_ime_settings), }; - final Context context = this; final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface di, int position) { @@ -2382,7 +2369,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction switch (position) { case 0: Intent intent = CompatUtils.getInputLanguageSelectionIntent( - ImfUtils.getInputMethodIdOfThisIme(context), + mRichImm.getInputMethodIdOfThisIme(), Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 75b67bfc6..23887c4d5 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -26,7 +26,6 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.research.ResearchLogger; @@ -399,7 +398,7 @@ public final class RichInputConnection { if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } - public CharSequence getNthPreviousWord(final String sentenceSeperators, final int n) { + public String getNthPreviousWord(final String sentenceSeperators, final int n) { mIC = mParent.getCurrentInputConnection(); if (null == mIC) return null; final CharSequence prev = mIC.getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); @@ -467,19 +466,22 @@ public final class RichInputConnection { // (n = 2) "abc|" -> null // (n = 2) "abc |" -> null // (n = 2) "abc. def|" -> null - public static CharSequence getNthPreviousWord(final CharSequence prev, + public static String getNthPreviousWord(final CharSequence prev, final String sentenceSeperators, final int n) { if (prev == null) return null; - String[] w = spaceRegex.split(prev); + final String[] w = spaceRegex.split(prev); // If we can't find n words, or we found an empty word, return null. - if (w.length < n || w[w.length - n].length() <= 0) return null; + if (w.length < n) return null; + final String nthPrevWord = w[w.length - n]; + final int length = nthPrevWord.length(); + if (length <= 0) return null; // If ends in a separator, return null - char lastChar = w[w.length - n].charAt(w[w.length - n].length() - 1); + final char lastChar = nthPrevWord.charAt(length - 1); if (sentenceSeperators.contains(String.valueOf(lastChar))) return null; - return w[w.length - n]; + return nthPrevWord; } /** @@ -512,19 +514,20 @@ public final class RichInputConnection { * be included in the returned range * @return a range containing the text surrounding the cursor */ - public Range getWordRangeAtCursor(String sep, int additionalPrecedingWordsCount) { + public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) { mIC = mParent.getCurrentInputConnection(); if (mIC == null || sep == null) { return null; } - CharSequence before = mIC.getTextBeforeCursor(1000, 0); - CharSequence after = mIC.getTextAfterCursor(1000, 0); + final CharSequence before = mIC.getTextBeforeCursor(1000, 0); + final CharSequence after = mIC.getTextAfterCursor(1000, 0); if (before == null || after == null) { return null; } // Going backward, alternate skipping non-separators and separators until enough words // have been read. + int count = additionalPrecedingWordsCount; int start = before.length(); boolean isStoppingAtWhitespace = true; // toggles to indicate what to stop at while (true) { // see comments below for why this is guaranteed to halt @@ -541,7 +544,7 @@ public final class RichInputConnection { // isStoppingAtWhitespace is true every other time through the loop, // so additionalPrecedingWordsCount is guaranteed to become < 0, which // guarantees outer loop termination - if (isStoppingAtWhitespace && (--additionalPrecedingWordsCount < 0)) { + if (isStoppingAtWhitespace && (--count < 0)) { break; // outer loop } isStoppingAtWhitespace = !isStoppingAtWhitespace; @@ -559,7 +562,7 @@ public final class RichInputConnection { } } - int cursor = getCursorPosition(); + final int cursor = getCursorPosition(); if (start >= 0 && cursor + end <= after.length() + before.length()) { String word = before.toString().substring(start, before.length()) + after.toString().substring(0, end); @@ -570,8 +573,8 @@ public final class RichInputConnection { } public boolean isCursorTouchingWord(final SettingsValues settingsValues) { - CharSequence before = getTextBeforeCursor(1, 0); - CharSequence after = getTextAfterCursor(1, 0); + final CharSequence before = getTextBeforeCursor(1, 0); + final CharSequence after = getTextAfterCursor(1, 0); if (!TextUtils.isEmpty(before) && !settingsValues.isWordSeparator(before.charAt(0)) && !settingsValues.isSymbolExcludedFromWordSeparators(before.charAt(0))) { return true; @@ -587,7 +590,7 @@ public final class RichInputConnection { if (DEBUG_BATCH_NESTING) checkBatchEdit(); final CharSequence lastOne = getTextBeforeCursor(1, 0); if (lastOne != null && lastOne.length() == 1 - && lastOne.charAt(0) == Keyboard.CODE_SPACE) { + && lastOne.charAt(0) == Constants.CODE_SPACE) { deleteSurroundingText(1, 0); } } @@ -613,7 +616,7 @@ public final class RichInputConnection { CharSequence word = getWordAtCursor(settings.mWordSeparators); // We don't suggest on leading single quotes, so we have to remove them from the word if // it starts with single quotes. - while (!TextUtils.isEmpty(word) && Keyboard.CODE_SINGLE_QUOTE == word.charAt(0)) { + while (!TextUtils.isEmpty(word) && Constants.CODE_SINGLE_QUOTE == word.charAt(0)) { word = word.subSequence(1, word.length()); } if (TextUtils.isEmpty(word)) return null; @@ -665,7 +668,7 @@ public final class RichInputConnection { // NOTE: This does not work with surrogate pairs. Hopefully when the keyboard is able to // enter surrogate pairs this code will have been removed. if (TextUtils.isEmpty(textBeforeCursor) - || (Keyboard.CODE_SPACE != textBeforeCursor.charAt(1))) { + || (Constants.CODE_SPACE != textBeforeCursor.charAt(1))) { // We may only come here if the application is changing the text while we are typing. // This is quite a broken case, but not logically impossible, so we shouldn't crash, // but some debugging log may be in order. diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java new file mode 100644 index 000000000..63dfd3250 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin; + +import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; + +import android.content.Context; +import android.os.IBinder; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; + +import java.util.Collections; +import java.util.List; + +/** + * Enrichment class for InputMethodManager to simplify interaction and add functionality. + */ +public final class RichInputMethodManager { + private static final String TAG = RichInputMethodManager.class.getSimpleName(); + + private RichInputMethodManager() { + // This utility class is not publicly instantiable. + } + + private static final RichInputMethodManager sInstance = new RichInputMethodManager(); + + private InputMethodManagerCompatWrapper mImmWrapper; + private InputMethodInfo mInputMethodInfoOfThisIme; + + public static RichInputMethodManager getInstance() { + sInstance.checkInitialized(); + return sInstance; + } + + public static void init(final Context context) { + sInstance.initInternal(context); + } + + private void checkInitialized() { + if (mImmWrapper == null) { + throw new RuntimeException(TAG + " is used before initialization"); + } + } + + private void initInternal(final Context context) { + mImmWrapper = new InputMethodManagerCompatWrapper(context); + mInputMethodInfoOfThisIme = getInputMethodInfoOfThisIme(context); + } + + public InputMethodManager getInputMethodManager() { + checkInitialized(); + return mImmWrapper.mImm; + } + + private InputMethodInfo getInputMethodInfoOfThisIme(final Context context) { + final String packageName = context.getPackageName(); + for (final InputMethodInfo imi : mImmWrapper.mImm.getInputMethodList()) { + if (imi.getPackageName().equals(packageName)) { + return imi; + } + } + throw new RuntimeException("Input method id for " + packageName + " not found."); + } + + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { + final boolean result = mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme); + if (!result) { + mImmWrapper.mImm.switchToLastInputMethod(token); + return false; + } + return true; + } + + public InputMethodInfo getInputMethodInfoOfThisIme() { + return mInputMethodInfoOfThisIme; + } + + public String getInputMethodIdOfThisIme() { + return mInputMethodInfoOfThisIme.getId(); + } + + public boolean checkIfSubtypeBelongsToThisImeAndEnabled(final InputMethodSubtype subtype) { + return checkIfSubtypeBelongsToImeAndEnabled(mInputMethodInfoOfThisIme, subtype); + } + + public boolean checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled( + final InputMethodSubtype subtype) { + final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); + final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList( + subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */)); + return subtypeEnabled && !subtypeExplicitlyEnabled; + } + + public boolean checkIfSubtypeBelongsToImeAndEnabled(final InputMethodInfo imi, + final InputMethodSubtype subtype) { + return checkIfSubtypeBelongsToList( + subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + imi, true /* allowsImplicitlySelectedSubtypes */)); + } + + private static boolean checkIfSubtypeBelongsToList(final InputMethodSubtype subtype, + final List<InputMethodSubtype> subtypes) { + for (final InputMethodSubtype ims : subtypes) { + if (ims.equals(subtype)) { + return true; + } + } + return false; + } + + public boolean checkIfSubtypeBelongsToThisIme(final InputMethodSubtype subtype) { + final InputMethodInfo myImi = mInputMethodInfoOfThisIme; + final int count = myImi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype ims = myImi.getSubtypeAt(i); + if (ims.equals(subtype)) { + return true; + } + } + return false; + } + + public InputMethodSubtype getCurrentInputMethodSubtype( + final InputMethodSubtype defaultSubtype) { + final InputMethodSubtype currentSubtype = mImmWrapper.mImm.getCurrentInputMethodSubtype(); + return (currentSubtype != null) ? currentSubtype : defaultSubtype; + } + + public boolean hasMultipleEnabledIMEsOrSubtypes(final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> enabledImis = mImmWrapper.mImm.getEnabledInputMethodList(); + return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, enabledImis); + } + + public boolean hasMultipleEnabledSubtypesInThisIme( + final boolean shouldIncludeAuxiliarySubtypes) { + final List<InputMethodInfo> imiList = Collections.singletonList(mInputMethodInfoOfThisIme); + return hasMultipleEnabledSubtypes(shouldIncludeAuxiliarySubtypes, imiList); + } + + private boolean hasMultipleEnabledSubtypes(final boolean shouldIncludeAuxiliarySubtypes, + final List<InputMethodInfo> imiList) { + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : imiList) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List<InputMethodSubtype> subtypes = + mImmWrapper.mImm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + if (filteredImisCount > 1) { + return true; + } + final List<InputMethodSubtype> subtypes = + mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true); + int keyboardCount = 0; + // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's + // both explicitly and implicitly enabled input method subtype. + // (The current IME should be LatinIME.) + for (InputMethodSubtype subtype : subtypes) { + if (KEYBOARD_MODE.equals(subtype.getMode())) { + ++keyboardCount; + } + } + return keyboardCount > 1; + } + + public InputMethodSubtype findSubtypeByLocaleAndKeyboardLayoutSet(final String localeString, + final String keyboardLayoutSetName) { + final InputMethodInfo myImi = mInputMethodInfoOfThisIme; + final int count = myImi.getSubtypeCount(); + for (int i = 0; i < count; i++) { + final InputMethodSubtype subtype = myImi.getSubtypeAt(i); + final String layoutName = SubtypeLocale.getKeyboardLayoutSetName(subtype); + if (localeString.equals(subtype.getLocale()) + && keyboardLayoutSetName.equals(layoutName)) { + return subtype; + } + } + return null; + } + + public void setInputMethodAndSubtype(final IBinder token, final InputMethodSubtype subtype) { + mImmWrapper.mImm.setInputMethodAndSubtype( + token, mInputMethodInfoOfThisIme.getId(), subtype); + } + + public void setAdditionalInputMethodSubtypes(final InputMethodSubtype[] subtypes) { + mImmWrapper.mImm.setAdditionalInputMethodSubtypes( + mInputMethodInfoOfThisIme.getId(), subtypes); + } +} diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 348928df8..fdad5430a 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -203,7 +203,8 @@ public final class Settings extends InputMethodSettingsFragment final Intent intent = dictionaryLink.getIntent(); final int number = context.getPackageManager().queryIntentActivities(intent, 0).size(); - if (0 >= number) { + // TODO: The experimental version is not supported by the Dictionary Pack Service yet + if (ProductionFlag.IS_EXPERIMENTAL || 0 >= number) { textCorrectionGroup.removePreference(dictionaryLink); } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 2f49fe92e..d6daa0d26 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -359,16 +359,15 @@ public final class SettingsValues { return prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true); } - public boolean isLanguageSwitchKeyEnabled(final Context context) { + public boolean isLanguageSwitchKeyEnabled() { if (!mShowsLanguageSwitchKey) { return false; } + final RichInputMethodManager imm = RichInputMethodManager.getInstance(); if (mIncludesOtherImesInLanguageSwitchList) { - return ImfUtils.hasMultipleEnabledIMEsOrSubtypes( - context, /* include aux subtypes */false); + return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */); } else { - return ImfUtils.hasMultipleEnabledSubtypesInThisIme( - context, /* include aux subtypes */false); + return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */); } } diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index df7709892..043043cef 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -16,10 +16,9 @@ package com.android.inputmethod.latin; +import android.text.InputType; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; // For character constants - import java.util.ArrayList; import java.util.Locale; @@ -28,30 +27,30 @@ public final class StringUtils { // This utility class is not publicly instantiable. } - public static int codePointCount(String text) { + public static int codePointCount(final String text) { if (TextUtils.isEmpty(text)) return 0; return text.codePointCount(0, text.length()); } - public static boolean containsInArray(String key, String[] array) { + public static boolean containsInArray(final String key, final String[] array) { for (final String element : array) { if (key.equals(element)) return true; } return false; } - public static boolean containsInCsv(String key, String csv) { + public static boolean containsInCsv(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return false; return containsInArray(key, csv.split(",")); } - public static String appendToCsvIfNotExists(String key, String csv) { + public static String appendToCsvIfNotExists(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return key; if (containsInCsv(key, csv)) return csv; return csv + "," + key; } - public static String removeFromCsvIfExists(String key, String csv) { + public static String removeFromCsvIfExists(final String key, final String csv) { if (TextUtils.isEmpty(csv)) return ""; final String[] elements = csv.split(","); if (!containsInArray(key, elements)) return csv; @@ -63,82 +62,20 @@ public final class StringUtils { } /** - * Returns true if a and b are equal ignoring the case of the character. - * @param a first character to check - * @param b second character to check - * @return {@code true} if a and b are equal, {@code false} otherwise. - */ - public static boolean equalsIgnoreCase(char a, char b) { - // Some language, such as Turkish, need testing both cases. - return a == b - || Character.toLowerCase(a) == Character.toLowerCase(b) - || Character.toUpperCase(a) == Character.toUpperCase(b); - } - - /** - * Returns true if a and b are equal ignoring the case of the characters, including if they are - * both null. - * @param a first CharSequence to check - * @param b second CharSequence to check - * @return {@code true} if a and b are equal, {@code false} otherwise. - */ - public static boolean equalsIgnoreCase(CharSequence a, CharSequence b) { - if (a == b) - return true; // including both a and b are null. - if (a == null || b == null) - return false; - final int length = a.length(); - if (length != b.length()) - return false; - for (int i = 0; i < length; i++) { - if (!equalsIgnoreCase(a.charAt(i), b.charAt(i))) - return false; - } - return true; - } - - /** - * Returns true if a and b are equal ignoring the case of the characters, including if a is null - * and b is zero length. - * @param a CharSequence to check - * @param b character array to check - * @param offset start offset of array b - * @param length length of characters in array b - * @return {@code true} if a and b are equal, {@code false} otherwise. - * @throws IndexOutOfBoundsException - * if {@code offset < 0 || length < 0 || offset + length > data.length}. - * @throws NullPointerException if {@code b == null}. - */ - public static boolean equalsIgnoreCase(CharSequence a, char[] b, int offset, int length) { - if (offset < 0 || length < 0 || length > b.length - offset) - throw new IndexOutOfBoundsException("array.length=" + b.length + " offset=" + offset - + " length=" + length); - if (a == null) - return length == 0; // including a is null and b is zero length. - if (a.length() != length) - return false; - for (int i = 0; i < length; i++) { - if (!equalsIgnoreCase(a.charAt(i), b[offset + i])) - return false; - } - return true; - } - - /** * Remove duplicates from an array of strings. * * This method will always keep the first occurrence of all strings at their position * in the array, removing the subsequent ones. */ - public static void removeDupes(final ArrayList<CharSequence> suggestions) { + public static void removeDupes(final ArrayList<String> suggestions) { if (suggestions.size() < 2) return; int i = 1; // Don't cache suggestions.size(), since we may be removing items while (i < suggestions.size()) { - final CharSequence cur = suggestions.get(i); + final String cur = suggestions.get(i); // Compare each suggestion with each previous suggestion for (int j = 0; j < i; j++) { - CharSequence previous = suggestions.get(j); + final String previous = suggestions.get(j); if (TextUtils.equals(cur, previous)) { suggestions.remove(i); i--; @@ -149,7 +86,7 @@ public final class StringUtils { } } - public static String toTitleCase(String s, Locale locale) { + public static String toTitleCase(final String s, final Locale locale) { if (s.length() <= 1) { // TODO: is this really correct? Shouldn't this be s.toUpperCase()? return s; @@ -165,21 +102,19 @@ public final class StringUtils { return s.toUpperCase(locale).charAt(0) + s.substring(1); } + private static final int[] EMPTY_CODEPOINTS = {}; + public static int[] toCodePointArray(final String string) { - final char[] characters = string.toCharArray(); - final int length = characters.length; - final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; + final int length = string.length(); if (length <= 0) { - return new int[0]; + return EMPTY_CODEPOINTS; } - int codePoint = Character.codePointAt(characters, 0); - int dsti = 0; - for (int srci = Character.charCount(codePoint); - srci < length; srci += Character.charCount(codePoint), ++dsti) { - codePoints[dsti] = codePoint; - codePoint = Character.codePointAt(characters, srci); + final int[] codePoints = new int[string.codePointCount(0, length)]; + int destIndex = 0; + for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) { + codePoints[destIndex] = string.codePointAt(index); + destIndex++; } - codePoints[dsti] = codePoint; return codePoints; } @@ -237,7 +172,7 @@ public final class StringUtils { } else { for (i = cs.length(); i > 0; i--) { final char c = cs.charAt(i - 1); - if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE && Character.getType(c) != Character.START_PUNCTUATION) { break; } @@ -253,11 +188,11 @@ public final class StringUtils { // if the first char that's not a space or tab is a start of line (as in \n, start of text, // or some other similar characters). int j = i; - char prevChar = Keyboard.CODE_SPACE; + char prevChar = Constants.CODE_SPACE; if (hasSpaceBefore) --j; while (j > 0) { prevChar = cs.charAt(j - 1); - if (!Character.isSpaceChar(prevChar) && prevChar != Keyboard.CODE_TAB) break; + if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break; j--; } if (j <= 0 || Character.isWhitespace(prevChar)) { @@ -296,7 +231,7 @@ public final class StringUtils { // variants of English, the final period is placed within double quotes and maybe // other closing punctuation signs. This is generally not true in other languages. final char c = cs.charAt(j - 1); - if (c != Keyboard.CODE_DOUBLE_QUOTE && c != Keyboard.CODE_SINGLE_QUOTE + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE && Character.getType(c) != Character.END_PUNCTUATION) { break; } @@ -310,10 +245,10 @@ public final class StringUtils { // end of a sentence. If we have a question mark or an exclamation mark, it's the end of // a sentence. If it's neither, the only remaining case is the period so we get the opposite // case out of the way. - if (c == Keyboard.CODE_QUESTION_MARK || c == Keyboard.CODE_EXCLAMATION_MARK) { + if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) { return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes; } - if (c != Keyboard.CODE_PERIOD || j <= 0) { + if (c != Constants.CODE_PERIOD || j <= 0) { return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; } @@ -363,7 +298,7 @@ public final class StringUtils { case WORD: if (Character.isLetter(c)) { state = WORD; - } else if (c == Keyboard.CODE_PERIOD) { + } else if (c == Constants.CODE_PERIOD) { state = PERIOD; } else { return caps; @@ -379,7 +314,7 @@ public final class StringUtils { case LETTER: if (Character.isLetter(c)) { state = LETTER; - } else if (c == Keyboard.CODE_PERIOD) { + } else if (c == Constants.CODE_PERIOD) { state = PERIOD; } else { return noCaps; diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java index 579f96bb4..5d8c0b17d 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -151,18 +151,19 @@ public final class SubtypeLocale { // InputMethodSubtype's display name in its locale. // isAdditionalSubtype (T=true, F=false) - // locale layout | display name - // ------ ------ - ---------------------- - // en_US qwerty F English (US) exception - // en_GB qwerty F English (UK) exception - // fr azerty F Français - // fr_CA qwerty F Français (Canada) - // de qwertz F Deutsch - // zz qwerty F No language (QWERTY) in system locale - // fr qwertz T Français (QWERTZ) - // de qwerty T Deutsch (QWERTY) - // en_US azerty T English (US) (AZERTY) - // zz azerty T No language (AZERTY) in system locale + // locale layout | display name + // ------ ------- - ---------------------- + // en_US qwerty F English (US) exception + // en_GB qwerty F English (UK) exception + // es_US spanish F Español (EE.UU.) exception + // fr azerty F Français + // fr_CA qwerty F Français (Canada) + // de qwertz F Deutsch + // zz qwerty F No language (QWERTY) in system locale + // fr qwertz T Français (QWERTZ) + // de qwerty T Deutsch (QWERTY) + // en_US azerty T English (US) (AZERTY) + // zz azerty T No language (AZERTY) in system locale public static String getSubtypeDisplayName(final InputMethodSubtype subtype, Resources res) { final String replacementString = (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15 diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 8e51a372b..8f2e27549 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -43,7 +43,7 @@ public final class SubtypeSwitcher { private static final String TAG = SubtypeSwitcher.class.getSimpleName(); private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); - private /* final */ InputMethodManager mImm; + private /* final */ RichInputMethodManager mRichImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; @@ -84,7 +84,7 @@ public final class SubtypeSwitcher { public static void init(final Context context) { SubtypeLocale.init(context); sInstance.initialize(context); - sInstance.updateAllParameters(context); + sInstance.updateAllParameters(); } private SubtypeSwitcher() { @@ -93,13 +93,13 @@ public final class SubtypeSwitcher { private void initialize(final Context service) { mResources = service.getResources(); - mImm = ImfUtils.getInputMethodManager(service); + mRichImm = RichInputMethodManager.getInstance(); mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); mCurrentSystemLocale = mResources.getConfiguration().locale; - mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); - mCurrentSubtype = ImfUtils.getCurrentInputMethodSubtype(service, mNoLanguageSubtype); + mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); + mCurrentSubtype = mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype); if (mNoLanguageSubtype == null) { throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); } @@ -110,9 +110,9 @@ public final class SubtypeSwitcher { // Update all parameters stored in SubtypeSwitcher. // Only configuration changed event is allowed to call this because this is heavy. - private void updateAllParameters(final Context context) { + private void updateAllParameters() { mCurrentSystemLocale = mResources.getConfiguration().locale; - updateSubtype(ImfUtils.getCurrentInputMethodSubtype(context, mNoLanguageSubtype)); + updateSubtype(mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype)); updateParametersOnStartInputViewAndReturnIfCurrentSubtypeEnabled(); } @@ -137,7 +137,7 @@ public final class SubtypeSwitcher { */ private boolean updateEnabledSubtypesAndReturnIfEnabled(final InputMethodSubtype subtype) { final List<InputMethodSubtype> enabledSubtypesOfThisIme = - mImm.getEnabledInputMethodSubtypeList(null, true); + mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true); mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); for (final InputMethodSubtype ims : enabledSubtypesOfThisIme) { @@ -162,7 +162,7 @@ public final class SubtypeSwitcher { } // TODO: Update an icon for shortcut IME final Map<InputMethodInfo, List<InputMethodSubtype>> shortcuts = - mImm.getShortcutInputMethodsAndSubtypes(); + mRichImm.getInputMethodManager().getShortcutInputMethodsAndSubtypes(); mShortcutInputMethodInfo = null; mShortcutSubtype = null; for (final InputMethodInfo imi : shortcuts.keySet()) { @@ -193,8 +193,13 @@ public final class SubtypeSwitcher { } final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype); + final boolean sameLocale = mCurrentSystemLocale.equals(newLocale); + final boolean sameLanguage = mCurrentSystemLocale.getLanguage().equals( + newLocale.getLanguage()); + final boolean implicitlyEnabled = + mRichImm.checkIfSubtypeBelongsToThisImeAndImplicitlyEnabled(newSubtype); mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( - mCurrentSystemLocale.equals(newLocale)); + sameLocale || (sameLanguage && implicitlyEnabled)); if (newSubtype.equals(mCurrentSubtype)) return; @@ -221,7 +226,7 @@ public final class SubtypeSwitcher { if (token == null) { return; } - final InputMethodManager imm = mImm; + final InputMethodManager imm = mRichImm.getInputMethodManager(); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { @@ -238,14 +243,8 @@ public final class SubtypeSwitcher { 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; + return mRichImm.checkIfSubtypeBelongsToImeAndEnabled( + mShortcutInputMethodInfo, mShortcutSubtype); } public boolean isShortcutImeReady() { @@ -285,12 +284,12 @@ public final class SubtypeSwitcher { return SubtypeLocale.getSubtypeLocale(mCurrentSubtype); } - public boolean onConfigurationChanged(final Configuration conf, final Context context) { + public boolean onConfigurationChanged(final Configuration conf) { final Locale systemLocale = conf.locale; final boolean systemLocaleChanged = !systemLocale.equals(mCurrentSystemLocale); // If system configuration was changed, update all parameters. if (systemLocaleChanged) { - updateAllParameters(context); + updateAllParameters(); } return systemLocaleChanged; } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index f0e3b4ebd..3dc2ba95b 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -19,7 +19,7 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -38,7 +38,7 @@ public final class Suggest { public static final String TAG = Suggest.class.getSimpleName(); // Session id for - // {@link #getSuggestedWords(WordComposer,CharSequence,ProximityInfo,boolean,int)}. + // {@link #getSuggestedWords(WordComposer,String,ProximityInfo,boolean,int)}. public static final int SESSION_TYPING = 0; public static final int SESSION_GESTURE = 1; @@ -71,7 +71,8 @@ public final class Suggest { mLocale = locale; } - /* package for test */ Suggest(final Context context, final File dictionary, + @UsedForTesting + Suggest(final Context context, final File dictionary, final long startOffset, final long length, final Locale locale) { final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset, length /* useFullEditDistance */, false, locale); @@ -138,7 +139,7 @@ public final class Suggest { * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted * before the main dictionary, if set. This refers to the system-managed user dictionary. */ - public void setUserDictionary(UserBinaryDictionary userDictionary) { + public void setUserDictionary(final UserBinaryDictionary userDictionary) { addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER, userDictionary); } @@ -147,12 +148,12 @@ public final class Suggest { * the contacts dictionary by passing null to this method. In this case no contacts dictionary * won't be used. */ - public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) { + public void setContactsDictionary(final ContactsBinaryDictionary contactsDictionary) { mContactsDict = contactsDictionary; addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_CONTACTS, contactsDictionary); } - public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) { + public void setUserHistoryDictionary(final UserHistoryDictionary userHistoryDictionary) { addOrReplaceDictionary(mDictionaries, Dictionary.TYPE_USER_HISTORY, userHistoryDictionary); } @@ -160,9 +161,9 @@ public final class Suggest { mAutoCorrectionThreshold = threshold; } - public SuggestedWords getSuggestedWords( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final boolean isCorrectionEnabled, int sessionId) { + public SuggestedWords getSuggestedWords(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final boolean isCorrectionEnabled, final int sessionId) { LatinImeLogger.onStartSuggestion(prevWordForBigram); if (wordComposer.isBatchMode()) { return getSuggestedWordsForBatchInput( @@ -174,9 +175,9 @@ public final class Suggest { } // Retrieves suggestions for the typing input. - private SuggestedWords getSuggestedWordsForTypingInput( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) { + private SuggestedWords getSuggestedWordsForTypingInput(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final boolean isCorrectionEnabled) { final int trailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount(); final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -203,7 +204,7 @@ public final class Suggest { wordComposerForLookup, prevWordForBigram, proximityInfo)); } - final CharSequence whitelistedWord; + final String whitelistedWord; if (suggestionsSet.isEmpty()) { whitelistedWord = null; } else if (SuggestedWordInfo.KIND_WHITELIST != suggestionsSet.first().mKind) { @@ -287,9 +288,9 @@ public final class Suggest { } // Retrieves suggestions for the batch input. - private SuggestedWords getSuggestedWordsForBatchInput( - final WordComposer wordComposer, CharSequence prevWordForBigram, - final ProximityInfo proximityInfo, int sessionId) { + private SuggestedWords getSuggestedWordsForBatchInput(final WordComposer wordComposer, + final String prevWordForBigram, final ProximityInfo proximityInfo, + final int sessionId) { final BoundedTreeSet suggestionsSet = new BoundedTreeSet(sSuggestedWordInfoComparator, MAX_SUGGESTIONS); @@ -307,7 +308,7 @@ public final class Suggest { } for (SuggestedWordInfo wordInfo : suggestionsSet) { - LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), wordInfo.mSourceDict); + LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict); } final ArrayList<SuggestedWordInfo> suggestionsContainer = @@ -372,7 +373,7 @@ public final class Suggest { if (o1.mScore < o2.mScore) return 1; if (o1.mCodePointCount < o2.mCodePointCount) return -1; if (o1.mCodePointCount > o2.mCodePointCount) return 1; - return o1.mWord.toString().compareTo(o2.mWord.toString()); + return o1.mWord.compareTo(o2.mWord); } } private static final SuggestedWordInfoComparator sSuggestedWordInfoComparator = @@ -383,16 +384,17 @@ public final class Suggest { final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { final StringBuilder sb = new StringBuilder(wordInfo.mWord.length()); if (isAllUpperCase) { - sb.append(wordInfo.mWord.toString().toUpperCase(locale)); + sb.append(wordInfo.mWord.toUpperCase(locale)); } else if (isFirstCharCapitalized) { - sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale)); + sb.append(StringUtils.toTitleCase(wordInfo.mWord, locale)); } else { sb.append(wordInfo.mWord); } for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) { - sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE); + sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE); } - return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind, wordInfo.mSourceDict); + return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind, + wordInfo.mSourceDict); } public void close() { diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 52e292a86..572f2906e 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -53,6 +53,10 @@ public final class SuggestedWords { mIsPrediction = isPrediction; } + public boolean isEmpty() { + return mSuggestedWordInfoList.isEmpty(); + } + public int size() { return mSuggestedWordInfoList.size(); } @@ -86,11 +90,14 @@ public final class SuggestedWords { public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( final CompletionInfo[] infos) { final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList(); - for (CompletionInfo info : infos) { - if (null != info && info.getText() != null) { - result.add(new SuggestedWordInfo(info.getText(), SuggestedWordInfo.MAX_SCORE, - SuggestedWordInfo.KIND_APP_DEFINED, Dictionary.TYPE_APPLICATION_DEFINED)); - } + for (final CompletionInfo info : infos) { + if (info == null) continue; + final CharSequence text = info.getText(); + if (null == text) continue; + final SuggestedWordInfo suggestedWordInfo = new SuggestedWordInfo(text.toString(), + SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_APP_DEFINED, + Dictionary.TYPE_APPLICATION_DEFINED); + result.add(suggestedWordInfo); } return result; } @@ -98,7 +105,7 @@ public final class SuggestedWords { // Should get rid of the first one (what the user typed previously) from suggestions // and replace it with what the user currently typed. public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( - final CharSequence typedWord, final SuggestedWords previousSuggestions) { + final String typedWord, final SuggestedWords previousSuggestions) { final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList(); final HashSet<String> alreadySeen = CollectionUtils.newHashSet(); suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, @@ -107,7 +114,7 @@ public final class SuggestedWords { final int previousSize = previousSuggestions.size(); for (int pos = 1; pos < previousSize; pos++) { final SuggestedWordInfo prevWordInfo = previousSuggestions.getWordInfo(pos); - final String prevWord = prevWordInfo.mWord.toString(); + final String prevWord = prevWordInfo.mWord; // Filter out duplicate suggestion. if (!alreadySeen.contains(prevWord)) { suggestionsList.add(prevWordInfo); @@ -135,9 +142,9 @@ public final class SuggestedWords { public final String mSourceDict; private String mDebugString = ""; - public SuggestedWordInfo(final CharSequence word, final int score, final int kind, + public SuggestedWordInfo(final String word, final int score, final int kind, final String sourceDict) { - mWord = word.toString(); + mWord = word; mScore = score; mKind = kind; mSourceDict = sourceDict; @@ -145,7 +152,7 @@ public final class SuggestedWords { } - public void setDebugString(String str) { + public void setDebugString(final String str) { if (null == str) throw new NullPointerException("Debug info is null"); mDebugString = str; } @@ -167,7 +174,7 @@ public final class SuggestedWords { if (TextUtils.isEmpty(mDebugString)) { return mWord; } else { - return mWord + " (" + mDebugString.toString() + ")"; + return mWord + " (" + mDebugString + ")"; } } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java index 8f21b7b4a..ec4dc1436 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedContactsBinaryDictionary.java @@ -33,13 +33,13 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB @Override public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo); } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { syncReloadDictionaryIfRequired(); return isValidWordInner(word); } diff --git a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java index 612f54d73..4bdaf2039 100644 --- a/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/SynchronouslyLoadedUserBinaryDictionary.java @@ -36,13 +36,13 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic @Override public synchronized ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer codes, - final CharSequence prevWordForBigrams, final ProximityInfo proximityInfo) { + final String prevWordForBigrams, final ProximityInfo proximityInfo) { syncReloadDictionaryIfRequired(); return super.getSuggestions(codes, prevWordForBigrams, proximityInfo); } @Override - public synchronized boolean isValidWord(CharSequence word) { + public synchronized boolean isValidWord(final String word) { syncReloadDictionaryIfRequired(); return isValidWordInner(word); } diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 60e6fa127..00c3cbe0a 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -200,7 +200,7 @@ public class UserBinaryDictionary extends ExpandableBinaryDictionary { mContext.startActivity(intent); } - private void addWords(Cursor cursor) { + private void addWords(final Cursor cursor) { // 16 is JellyBean, but we want this to compile against ICS. final boolean hasShortcutColumn = android.os.Build.VERSION.SDK_INT >= 16; clearFusionDictionary(); diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java index e39011145..787197755 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; @@ -129,7 +130,8 @@ public final class UserHistoryDictIOUtils { /** * Constructs a new FusionDictionary from BigramDictionaryInterface. */ - /* packages for test */ static FusionDictionary constructFusionDictionary( + @UsedForTesting + static FusionDictionary constructFusionDictionary( final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { final FusionDictionary fusionDict = new FusionDictionary(new Node(), new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, @@ -193,7 +195,8 @@ public final class UserHistoryDictIOUtils { /** * Adds all unigrams and bigrams in maps to OnAddWordListener. */ - /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams, + @UsedForTesting + static void addWordsFromWordMap(final Map<Integer, String> unigrams, final Map<Integer, Integer> frequencies, final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) { for (Map.Entry<Integer, String> entry : unigrams.entrySet()) { diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index 3615fa1fb..4fd9bfafb 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.os.AsyncTask; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface; @@ -75,7 +76,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { private final SharedPreferences mPrefs; // Should always be false except when we use this class for test - /* package for test */ boolean isTest = false; + @UsedForTesting boolean isTest = false; private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> sLangDictCache = CollectionUtils.newConcurrentHashMap(); @@ -122,7 +123,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { @Override protected ArrayList<SuggestedWordInfo> getWordsInner(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { // Inhibit suggestions (not predictions) for user history for now. Removing this method // is enough to use it through the standard ExpandableDictionary way. return null; @@ -132,7 +133,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { * Return whether the passed charsequence is in the dictionary. */ @Override - public synchronized boolean isValidWord(final CharSequence word) { + public synchronized boolean isValidWord(final String word) { // TODO: figure out what is the correct thing to do here. return false; } @@ -145,7 +146,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { * context, as in beginning of a sentence for example. * The second word may not be null (a NullPointerException would be thrown). */ - public int addToUserHistory(final String word1, String word2, boolean isValid) { + public int addToUserHistory(final String word1, final String word2, final boolean isValid) { if (word2.length() >= BinaryDictionary.MAX_WORD_LENGTH || (word1 != null && word1.length() >= BinaryDictionary.MAX_WORD_LENGTH)) { return -1; @@ -175,7 +176,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { return -1; } - public boolean cancelAddingUserHistory(String word1, String word2) { + public boolean cancelAddingUserHistory(final String word1, final String word2) { if (mBigramListLock.tryLock()) { try { if (mBigramList.removeBigram(word1, word2)) { @@ -226,7 +227,8 @@ public final class UserHistoryDictionary extends ExpandableDictionary { final ExpandableDictionary dictionary = this; final OnAddWordListener listener = new OnAddWordListener() { @Override - public void setUnigram(String word, String shortcutTarget, int frequency) { + public void setUnigram(final String word, final String shortcutTarget, + final int frequency) { profTotal++; if (DBG_SAVE_RESTORE) { Log.d(TAG, "load unigram: " + word + "," + frequency); @@ -236,7 +238,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - public void setBigram(String word1, String word2, int frequency) { + public void setBigram(final String word1, final String word2, final int frequency) { if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { profTotal++; @@ -250,7 +252,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { mBigramList.addBigram(word1, word2, (byte)frequency); } }; - + // Load the dictionary from binary file FileInputStream inStream = null; try { @@ -292,8 +294,9 @@ public final class UserHistoryDictionary extends ExpandableDictionary { private final SharedPreferences mPrefs; private final Context mContext; - public UpdateBinaryTask(UserHistoryDictionaryBigramList pendingWrites, String locale, - UserHistoryDictionary dict, SharedPreferences prefs, Context context) { + public UpdateBinaryTask(final UserHistoryDictionaryBigramList pendingWrites, + final String locale, final UserHistoryDictionary dict, + final SharedPreferences prefs, final Context context) { mBigramList = pendingWrites; mLocale = locale; mUserHistoryDictionary = dict; @@ -303,7 +306,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - protected Void doInBackground(Void... v) { + protected Void doInBackground(final Void... v) { if (mUserHistoryDictionary.isTest) { // If isTest == true, wait until the lock is released. mUserHistoryDictionary.mBigramListLock.lock(); @@ -360,7 +363,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } @Override - public int getFrequency(String word1, String word2) { + public int getFrequency(final String word1, final String word2) { final int freq; if (word1 == null) { // unigram freq = FREQUENCY_FOR_TYPED; @@ -390,6 +393,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { } } + @UsedForTesting void forceAddWordForTest(final String word1, final String word2, final boolean isValid) { mBigramListLock.lock(); try { diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index da0071adc..daff442f3 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -38,7 +38,7 @@ public final class WordComposer { private int[] mPrimaryKeyCodes; private final InputPointers mInputPointers = new InputPointers(N); private final StringBuilder mTypedWord; - private CharSequence mAutoCorrection; + private String mAutoCorrection; private boolean mIsResumed; private boolean mIsBatchMode; @@ -64,7 +64,7 @@ public final class WordComposer { refreshSize(); } - public WordComposer(WordComposer source) { + public WordComposer(final WordComposer source) { mPrimaryKeyCodes = Arrays.copyOf(source.mPrimaryKeyCodes, source.mPrimaryKeyCodes.length); mTypedWord = new StringBuilder(source.mTypedWord); mInputPointers.copy(source.mInputPointers); @@ -121,7 +121,8 @@ public final class WordComposer { return mInputPointers; } - private static boolean isFirstCharCapitalized(int index, int codePoint, boolean previous) { + private static boolean isFirstCharCapitalized(final int index, final int codePoint, + final boolean previous) { if (index == 0) return Character.isUpperCase(codePoint); return previous && !Character.isUpperCase(codePoint); } @@ -129,12 +130,12 @@ public final class WordComposer { /** * Add a new keystroke, with the pressed key's code point with the touch point coordinates. */ - public void add(int primaryCode, int keyX, int keyY) { + public void add(final int primaryCode, final int keyX, final int keyY) { final int newIndex = size(); mTypedWord.appendCodePoint(primaryCode); refreshSize(); if (newIndex < BinaryDictionary.MAX_WORD_LENGTH) { - mPrimaryKeyCodes[newIndex] = primaryCode >= Keyboard.CODE_SPACE + mPrimaryKeyCodes[newIndex] = primaryCode >= Constants.CODE_SPACE ? Character.toLowerCase(primaryCode) : primaryCode; // In the batch input mode, the {@code mInputPointers} holds batch input points and // shouldn't be overridden by the "typed key" coordinates @@ -148,7 +149,7 @@ public final class WordComposer { newIndex, primaryCode, mIsFirstCharCapitalized); if (Character.isUpperCase(primaryCode)) mCapsCount++; if (Character.isDigit(primaryCode)) mDigitsCount++; - if (Keyboard.CODE_SINGLE_QUOTE == primaryCode) { + if (Constants.CODE_SINGLE_QUOTE == primaryCode) { ++mTrailingSingleQuotesCount; } else { mTrailingSingleQuotesCount = 0; @@ -156,12 +157,12 @@ public final class WordComposer { mAutoCorrection = null; } - public void setBatchInputPointers(InputPointers batchPointers) { + public void setBatchInputPointers(final InputPointers batchPointers) { mInputPointers.set(batchPointers); mIsBatchMode = true; } - public void setBatchInputWord(CharSequence word) { + public void setBatchInputWord(final String word) { reset(); mIsBatchMode = true; final int length = word.length(); @@ -235,7 +236,7 @@ public final class WordComposer { int i = mTypedWord.length(); while (i > 0) { i = mTypedWord.offsetByCodePoints(i, -1); - if (Keyboard.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break; + if (Constants.CODE_SINGLE_QUOTE != mTypedWord.codePointAt(i)) break; ++mTrailingSingleQuotesCount; } } @@ -321,14 +322,14 @@ public final class WordComposer { /** * Sets the auto-correction for this word. */ - public void setAutoCorrection(final CharSequence correction) { + public void setAutoCorrection(final String correction) { mAutoCorrection = correction; } /** * @return the auto-correction for this word, or null if none. */ - public CharSequence getAutoCorrectionOrNull() { + public String getAutoCorrectionOrNull() { return mAutoCorrection; } @@ -341,7 +342,7 @@ public final class WordComposer { // `type' should be one of the LastComposedWord.COMMIT_TYPE_* constants above. public LastComposedWord commitWord(final int type, final String committedWord, - final String separatorString, final CharSequence prevWord) { + final String separatorString, final String prevWord) { // Note: currently, we come here whenever we commit a word. If it's a MANUAL_PICK // or a DECIDED_WORD we may cancel the commit later; otherwise, we should deactivate // the last composed word to ensure this does not happen. diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index 7b0231a6b..ee0e9cd7e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -16,19 +16,32 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding; import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.Map; import java.util.Stack; public final class BinaryDictIOUtils { private static final boolean DBG = false; + private static final int MSB24 = 0x800000; + private static final int SINT24_MAX = 0x7FFFFF; + private static final int MAX_JUMPS = 10000; + + private BinaryDictIOUtils() { + // This utility class is not publicly instantiable. + } private static final class Position { public static final int NOT_READ_GROUPCOUNT = -1; @@ -90,7 +103,9 @@ public final class BinaryDictIOUtils { final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags, formatOptions); - if (!isMovedGroup + final boolean isDeletedGroup = BinaryDictInputOutput.isDeletedGroup(info.mFlags, + formatOptions); + if (!isMovedGroup && !isDeletedGroup && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word words.put(info.mOriginalAddress, new String(pushedChars, 0, index)); frequencies.put(info.mOriginalAddress, info.mFrequency); @@ -153,6 +168,7 @@ public final class BinaryDictIOUtils { * @throws IOException * @throws UnsupportedFormatException */ + @UsedForTesting public static int getTerminalPosition(final FusionDictionaryBufferInterface buffer, final String word) throws IOException, UnsupportedFormatException { if (word == null) return FormatSpec.NOT_VALID_WORD; @@ -165,19 +181,19 @@ public final class BinaryDictIOUtils { if (wordPos >= wordLen) return FormatSpec.NOT_VALID_WORD; do { - int groupOffset = buffer.position() - header.mHeaderSize; final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); - groupOffset += BinaryDictInputOutput.getGroupCountSize(charGroupCount); - boolean foundNextCharGroup = false; for (int i = 0; i < charGroupCount; ++i) { final int charGroupPos = buffer.position(); final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, buffer.position(), header.mFormatOptions); - if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, - header.mFormatOptions)) { - continue; - } + final boolean isMovedGroup = + BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, + header.mFormatOptions); + final boolean isDeletedGroup = + BinaryDictInputOutput.isDeletedGroup(currentInfo.mFlags, + header.mFormatOptions); + if (isMovedGroup) continue; boolean same = true; for (int p = 0, j = word.offsetByCodePoints(0, wordPos); p < currentInfo.mCharacters.length; @@ -192,7 +208,8 @@ public final class BinaryDictIOUtils { if (same) { // found the group matches the word. if (wordPos + currentInfo.mCharacters.length == wordLen) { - if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL) { + if (currentInfo.mFrequency == CharGroup.NOT_A_TERMINAL + || isDeletedGroup) { return FormatSpec.NOT_VALID_WORD; } else { return charGroupPos; @@ -206,7 +223,6 @@ public final class BinaryDictIOUtils { buffer.position(currentInfo.mChildrenAddress); break; } - groupOffset = currentInfo.mEndAddress; } // If we found the next char group, it is under the file pointer. @@ -228,6 +244,10 @@ public final class BinaryDictIOUtils { return FormatSpec.NOT_VALID_WORD; } + private static int markAsDeleted(final int flags) { + return (flags & (~FormatSpec.MASK_GROUP_ADDRESS_TYPE)) | FormatSpec.FLAG_IS_DELETED; + } + /** * Delete the word from the binary file. * @@ -236,6 +256,7 @@ public final class BinaryDictIOUtils { * @throws IOException * @throws UnsupportedFormatException */ + @UsedForTesting public static void deleteWord(final FusionDictionaryBufferInterface buffer, final String word) throws IOException, UnsupportedFormatException { buffer.position(0); @@ -245,21 +266,58 @@ public final class BinaryDictIOUtils { buffer.position(wordPosition); final int flags = buffer.readUnsignedByte(); - final int newFlags = flags ^ FormatSpec.FLAG_IS_TERMINAL; buffer.position(wordPosition); - buffer.put((byte)newFlags); + buffer.put((byte)markAsDeleted(flags)); } - private static void putSInt24(final FusionDictionaryBufferInterface buffer, + /** + * @return the size written, in bytes. Always 3 bytes. + */ + private static int writeSInt24ToBuffer(final FusionDictionaryBufferInterface buffer, final int value) { final int absValue = Math.abs(value); buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); buffer.put((byte)((absValue >> 8) & 0xFF)); buffer.put((byte)(absValue & 0xFF)); + return 3; + } + + /** + * @return the size written, in bytes. Always 3 bytes. + */ + private static int writeSInt24ToStream(final OutputStream destination, final int value) + throws IOException { + final int absValue = Math.abs(value); + destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); + destination.write((byte)((absValue >> 8) & 0xFF)); + destination.write((byte)(absValue & 0xFF)); + return 3; } /** - * Update a parent address in a CharGroup that is addressed by groupOriginAddress. + * @return the size written, in bytes. 1, 2, or 3 bytes. + */ + private static int writeVariableAddress(final OutputStream destination, final int value) + throws IOException { + switch (BinaryDictInputOutput.getByteSize(value)) { + case 1: + destination.write((byte)value); + break; + case 2: + destination.write((byte)(0xFF & (value >> 8))); + destination.write((byte)(0xFF & value)); + break; + case 3: + destination.write((byte)(0xFF & (value >> 16))); + destination.write((byte)(0xFF & (value >> 8))); + destination.write((byte)(0xFF & value)); + break; + } + return BinaryDictInputOutput.getByteSize(value); + } + + /** + * Update a parent address in a CharGroup that is referred to by groupOriginAddress. * * @param buffer the buffer to write. * @param groupOriginAddress the address of the group. @@ -275,8 +333,648 @@ public final class BinaryDictIOUtils { throw new RuntimeException("this file format does not support parent addresses"); } final int flags = buffer.readUnsignedByte(); + if (BinaryDictInputOutput.isMovedGroup(flags, formatOptions)) { + // if the group is moved, the parent address is stored in the destination group. + // We are guaranteed to process the destination group later, so there is no need to + // update anything here. + buffer.position(originalPosition); + return; + } + if (DBG) { + MakedictLog.d("update parent address flags=" + flags + ", " + groupOriginAddress); + } final int parentOffset = newParentAddress - groupOriginAddress; - putSInt24(buffer, parentOffset); + writeSInt24ToBuffer(buffer, parentOffset); + buffer.position(originalPosition); + } + + private static void skipCharGroup(final FusionDictionaryBufferInterface buffer, + final FormatOptions formatOptions) { + final int flags = buffer.readUnsignedByte(); + BinaryDictInputOutput.readParentAddress(buffer, formatOptions); + skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); + BinaryDictInputOutput.readChildrenAddress(buffer, flags, formatOptions); + if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte(); + if ((flags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) != 0) { + final int shortcutsSize = buffer.readUnsignedShort(); + buffer.position(buffer.position() + shortcutsSize + - FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE); + } + if ((flags & FormatSpec.FLAG_HAS_BIGRAMS) != 0) { + int bigramCount = 0; + while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + final int bigramFlags = buffer.readUnsignedByte(); + switch (bigramFlags & FormatSpec.MASK_ATTRIBUTE_ADDRESS_TYPE) { + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE: + buffer.readUnsignedByte(); + break; + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES: + buffer.readUnsignedShort(); + break; + case FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES: + buffer.readUnsignedInt24(); + break; + } + if ((bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT) == 0) break; + } + if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + throw new RuntimeException("Too many bigrams in a group."); + } + } + } + + /** + * Update parent addresses in a Node that is referred to by nodeOriginAddress. + * + * @param buffer the buffer to be modified. + * @param nodeOriginAddress the address of a modified Node. + * @param newParentAddress the address to be written. + * @param formatOptions file format options. + */ + public static void updateParentAddresses(final FusionDictionaryBufferInterface buffer, + final int nodeOriginAddress, final int newParentAddress, + final FormatOptions formatOptions) { + final int originalPosition = buffer.position(); + buffer.position(nodeOriginAddress); + do { + final int count = BinaryDictInputOutput.readCharGroupCount(buffer); + for (int i = 0; i < count; ++i) { + updateParentAddress(buffer, buffer.position(), newParentAddress, formatOptions); + skipCharGroup(buffer, formatOptions); + } + final int forwardLinkAddress = buffer.readUnsignedInt24(); + buffer.position(forwardLinkAddress); + } while (formatOptions.mSupportsDynamicUpdate + && buffer.position() != FormatSpec.NO_FORWARD_LINK_ADDRESS); + buffer.position(originalPosition); + } + + private static void skipString(final FusionDictionaryBufferInterface buffer, + final boolean hasMultipleChars) { + if (hasMultipleChars) { + int character = CharEncoding.readChar(buffer); + while (character != FormatSpec.INVALID_CHARACTER) { + character = CharEncoding.readChar(buffer); + } + } else { + CharEncoding.readChar(buffer); + } + } + + /** + * Write a string to a stream. + * + * @param destination the stream to write. + * @param word the string to be written. + * @return the size written, in bytes. + * @throws IOException + */ + private static int writeString(final OutputStream destination, final String word) + throws IOException { + int size = 0; + final int length = word.length(); + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + if (CharEncoding.getCharSize(codePoint) == 1) { + destination.write((byte)codePoint); + size++; + } else { + destination.write((byte)(0xFF & (codePoint >> 16))); + destination.write((byte)(0xFF & (codePoint >> 8))); + destination.write((byte)(0xFF & codePoint)); + size += 3; + } + } + destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR); + size += FormatSpec.GROUP_TERMINATOR_SIZE; + return size; + } + + /** + * Update a children address in a CharGroup that is addressed by groupOriginAddress. + * + * @param buffer the buffer to write. + * @param groupOriginAddress the address of the group. + * @param newChildrenAddress the absolute address of the child. + * @param formatOptions file format options. + */ + public static void updateChildrenAddress(final FusionDictionaryBufferInterface buffer, + final int groupOriginAddress, final int newChildrenAddress, + final FormatOptions formatOptions) { + final int originalPosition = buffer.position(); + buffer.position(groupOriginAddress); + final int flags = buffer.readUnsignedByte(); + final int parentAddress = BinaryDictInputOutput.readParentAddress(buffer, formatOptions); + skipString(buffer, (flags & FormatSpec.FLAG_HAS_MULTIPLE_CHARS) != 0); + if ((flags & FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte(); + final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS + ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position(); + writeSInt24ToBuffer(buffer, childrenOffset); buffer.position(originalPosition); } + + /** + * Write a char group to an output stream. + * A char group is an in-memory representation of a node in trie. + * A char group info is an on-disk representation of a node. + * + * @param destination the stream to write. + * @param info the char group info to be written. + * @return the size written, in bytes. + */ + public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info) + throws IOException { + int size = FormatSpec.GROUP_FLAGS_SIZE; + destination.write((byte)info.mFlags); + final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ? + FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress; + size += writeSInt24ToStream(destination, parentOffset); + + for (int i = 0; i < info.mCharacters.length; ++i) { + if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) { + destination.write((byte)info.mCharacters[i]); + size++; + } else { + size += writeSInt24ToStream(destination, info.mCharacters[i]); + } + } + if (info.mCharacters.length > 1) { + destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR); + size++; + } + + if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { + destination.write((byte)info.mFrequency); + size++; + } + + if (DBG) { + MakedictLog.d("writeCharGroup origin=" + info.mOriginalAddress + ", size=" + size + + ", child=" + info.mChildrenAddress + ", characters =" + + new String(info.mCharacters, 0, info.mCharacters.length)); + } + final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ? + 0 : info.mChildrenAddress - (info.mOriginalAddress + size); + writeSInt24ToStream(destination, childrenOffset); + size += FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; + + if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) { + final int shortcutListSize = + BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets); + destination.write((byte)(shortcutListSize >> 8)); + destination.write((byte)(shortcutListSize & 0xFF)); + size += 2; + final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator(); + while (shortcutIterator.hasNext()) { + final WeightedString target = shortcutIterator.next(); + destination.write((byte)BinaryDictInputOutput.makeShortcutFlags( + shortcutIterator.hasNext(), target.mFrequency)); + size++; + size += writeString(destination, target.mWord); + } + } + + if (info.mBigrams != null) { + // TODO: Consolidate this code with the code that computes the size of the bigram list + // in BinaryDictionaryInputOutput#computeActualNodeSize + for (int i = 0; i < info.mBigrams.size(); ++i) { + + final int bigramFrequency = info.mBigrams.get(i).mFrequency; + int bigramFlags = (i < info.mBigrams.size() - 1) + ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0; + size++; + final int bigramOffset = info.mBigrams.get(i).mAddress - (info.mOriginalAddress + + size); + bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0; + switch (BinaryDictInputOutput.getByteSize(bigramOffset)) { + case 1: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES; + break; + } + bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY; + destination.write((byte)bigramFlags); + size += writeVariableAddress(destination, Math.abs(bigramOffset)); + } + } + return size; + } + + @SuppressWarnings("unused") + private static void updateForwardLink(final FusionDictionaryBufferInterface buffer, + final int nodeOriginAddress, final int newNodeAddress, + final FormatOptions formatOptions) { + buffer.position(nodeOriginAddress); + int jumpCount = 0; + while (jumpCount++ < MAX_JUMPS) { + final int count = BinaryDictInputOutput.readCharGroupCount(buffer); + for (int i = 0; i < count; ++i) skipCharGroup(buffer, formatOptions); + final int forwardLinkAddress = buffer.readUnsignedInt24(); + if (forwardLinkAddress == FormatSpec.NO_FORWARD_LINK_ADDRESS) { + buffer.position(buffer.position() - FormatSpec.FORWARD_LINK_ADDRESS_SIZE); + writeSInt24ToBuffer(buffer, newNodeAddress); + return; + } + buffer.position(forwardLinkAddress); + } + if (DBG && jumpCount >= MAX_JUMPS) { + throw new RuntimeException("too many jumps, probably a bug."); + } + } + + /** + * Helper method to move a char group to the tail of the file. + */ + private static int moveCharGroup(final OutputStream destination, + final FusionDictionaryBufferInterface buffer, final CharGroupInfo info, + final int nodeOriginAddress, final int oldGroupAddress, + final FormatOptions formatOptions) throws IOException { + updateParentAddress(buffer, oldGroupAddress, buffer.limit() + 1, formatOptions); + buffer.position(oldGroupAddress); + final int currentFlags = buffer.readUnsignedByte(); + buffer.position(oldGroupAddress); + buffer.put((byte)(FormatSpec.FLAG_IS_MOVED | (currentFlags + & (~FormatSpec.MASK_MOVE_AND_DELETE_FLAG)))); + int size = FormatSpec.GROUP_FLAGS_SIZE; + updateForwardLink(buffer, nodeOriginAddress, buffer.limit(), formatOptions); + size += writeNode(destination, new CharGroupInfo[] { info }); + return size; + } + + /** + * Compute the size of the char group. + */ + private static int computeGroupSize(final CharGroupInfo info, + final FormatOptions formatOptions) { + int size = FormatSpec.GROUP_FLAGS_SIZE + FormatSpec.PARENT_ADDRESS_SIZE + + BinaryDictInputOutput.getGroupCharactersSize(info.mCharacters) + + BinaryDictInputOutput.getChildrenAddressSize(info.mFlags, formatOptions); + if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) { + size += FormatSpec.GROUP_FREQUENCY_SIZE; + } + if (info.mShortcutTargets != null && !info.mShortcutTargets.isEmpty()) { + size += BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets); + } + if (info.mBigrams != null) { + for (final PendingAttribute attr : info.mBigrams) { + size += FormatSpec.GROUP_FLAGS_SIZE; + size += BinaryDictInputOutput.getByteSize(attr.mAddress); + } + } + return size; + } + + /** + * Write a node to the stream. + * + * @param destination the stream to write. + * @param infos groups to be written. + * @return the size written, in bytes. + * @throws IOException + */ + private static int writeNode(final OutputStream destination, final CharGroupInfo[] infos) + throws IOException { + int size = BinaryDictInputOutput.getGroupCountSize(infos.length); + switch (BinaryDictInputOutput.getGroupCountSize(infos.length)) { + case 1: + destination.write((byte)infos.length); + break; + case 2: + destination.write((byte)(infos.length >> 8)); + destination.write((byte)(infos.length & 0xFF)); + break; + default: + throw new RuntimeException("Invalid group count size."); + } + for (final CharGroupInfo info : infos) size += writeCharGroup(destination, info); + writeSInt24ToStream(destination, FormatSpec.NO_FORWARD_LINK_ADDRESS); + return size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + } + + /** + * Move a group that is referred to by oldGroupOrigin to the tail of the file. + * And set the children address to the byte after the group. + * + * @param nodeOrigin the address of the tail of the file. + * @param characters + * @param length + * @param flags + * @param frequency + * @param parentAddress + * @param shortcutTargets + * @param bigrams + * @param destination the stream representing the tail of the file. + * @param buffer the buffer representing the (constant-size) body of the file. + * @param oldNodeOrigin + * @param oldGroupOrigin + * @param formatOptions + * @return the size written, in bytes. + * @throws IOException + */ + private static int moveGroup(final int nodeOrigin, final int[] characters, final int length, + final int flags, final int frequency, final int parentAddress, + final ArrayList<WeightedString> shortcutTargets, + final ArrayList<PendingAttribute> bigrams, final OutputStream destination, + final FusionDictionaryBufferInterface buffer, final int oldNodeOrigin, + final int oldGroupOrigin, final FormatOptions formatOptions) throws IOException { + int size = 0; + final int newGroupOrigin = nodeOrigin + 1; + final int[] writtenCharacters = Arrays.copyOfRange(characters, 0, length); + final CharGroupInfo tmpInfo = new CharGroupInfo(newGroupOrigin, -1 /* endAddress */, + flags, writtenCharacters, frequency, parentAddress, FormatSpec.NO_CHILDREN_ADDRESS, + shortcutTargets, bigrams); + size = computeGroupSize(tmpInfo, formatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newGroupOrigin, newGroupOrigin + size, + flags, writtenCharacters, frequency, parentAddress, + nodeOrigin + 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE, shortcutTargets, + bigrams); + moveCharGroup(destination, buffer, newInfo, oldNodeOrigin, oldGroupOrigin, formatOptions); + return 1 + size + FormatSpec.FORWARD_LINK_ADDRESS_SIZE; + } + + /** + * Insert a word into a binary dictionary. + * + * @param buffer + * @param destination + * @param word + * @param frequency + * @param bigramStrings + * @param shortcuts + * @throws IOException + * @throws UnsupportedFormatException + */ + // TODO: Support batch insertion. + // TODO: Remove @UsedForTesting once UserHistoryDictionary is implemented by BinaryDictionary. + @UsedForTesting + public static void insertWord(final FusionDictionaryBufferInterface buffer, + final OutputStream destination, final String word, final int frequency, + final ArrayList<WeightedString> bigramStrings, + final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, + final boolean isBlackListEntry) + throws IOException, UnsupportedFormatException { + final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>(); + if (bigramStrings != null) { + for (final WeightedString bigram : bigramStrings) { + int position = getTerminalPosition(buffer, bigram.mWord); + if (position == FormatSpec.NOT_VALID_WORD) { + // TODO: figure out what is the correct thing to do here. + } else { + bigrams.add(new PendingAttribute(bigram.mFrequency, position)); + } + } + } + + final boolean isTerminal = true; + final boolean hasBigrams = !bigrams.isEmpty(); + final boolean hasShortcuts = shortcuts != null && !shortcuts.isEmpty(); + + // find the insert position of the word. + if (buffer.position() != 0) buffer.position(0); + final FileHeader header = BinaryDictInputOutput.readHeader(buffer); + + int wordPos = 0, address = buffer.position(), nodeOriginAddress = buffer.position(); + final int[] codePoints = FusionDictionary.getCodePoints(word); + final int wordLen = codePoints.length; + + for (int depth = 0; depth < Constants.Dictionary.MAX_WORD_LENGTH; ++depth) { + if (wordPos >= wordLen) break; + nodeOriginAddress = buffer.position(); + int nodeParentAddress = -1; + final int charGroupCount = BinaryDictInputOutput.readCharGroupCount(buffer); + boolean foundNextGroup = false; + + for (int i = 0; i < charGroupCount; ++i) { + address = buffer.position(); + final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, + buffer.position(), header.mFormatOptions); + final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, + header.mFormatOptions); + if (isMovedGroup) continue; + nodeParentAddress = (currentInfo.mParentAddress == FormatSpec.NO_PARENT_ADDRESS) + ? FormatSpec.NO_PARENT_ADDRESS : currentInfo.mParentAddress + address; + boolean matched = true; + for (int p = 0; p < currentInfo.mCharacters.length; ++p) { + if (wordPos + p >= wordLen) { + /* + * splitting + * before + * abcd - ef + * + * insert "abc" + * + * after + * abc - d - ef + */ + final int newNodeAddress = buffer.limit(); + final int flags = BinaryDictInputOutput.makeCharGroupFlags(p > 1, + isTerminal, 0, hasShortcuts, hasBigrams, false /* isNotAWord */, + false /* isBlackListEntry */, header.mFormatOptions); + int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, flags, + frequency, nodeParentAddress, shortcuts, bigrams, destination, + buffer, nodeOriginAddress, address, header.mFormatOptions); + + final int[] characters2 = Arrays.copyOfRange(currentInfo.mCharacters, p, + currentInfo.mCharacters.length); + if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + updateParentAddresses(buffer, currentInfo.mChildrenAddress, + newNodeAddress + written + 1, header.mFormatOptions); + } + final CharGroupInfo newInfo2 = new CharGroupInfo( + newNodeAddress + written + 1, -1 /* endAddress */, + currentInfo.mFlags, characters2, currentInfo.mFrequency, + newNodeAddress + 1, currentInfo.mChildrenAddress, + currentInfo.mShortcutTargets, currentInfo.mBigrams); + writeNode(destination, new CharGroupInfo[] { newInfo2 }); + return; + } else if (codePoints[wordPos + p] != currentInfo.mCharacters[p]) { + if (p > 0) { + /* + * splitting + * before + * ab - cd + * + * insert "ac" + * + * after + * a - b - cd + * | + * - c + */ + + final int newNodeAddress = buffer.limit(); + final int childrenAddress = currentInfo.mChildrenAddress; + + // move prefix + final int prefixFlags = BinaryDictInputOutput.makeCharGroupFlags(p > 1, + false /* isTerminal */, 0 /* childrenAddressSize*/, + false /* hasShortcut */, false /* hasBigrams */, + false /* isNotAWord */, false /* isBlackListEntry */, + header.mFormatOptions); + int written = moveGroup(newNodeAddress, currentInfo.mCharacters, p, + prefixFlags, -1 /* frequency */, nodeParentAddress, null, null, + destination, buffer, nodeOriginAddress, address, + header.mFormatOptions); + + final int[] suffixCharacters = Arrays.copyOfRange( + currentInfo.mCharacters, p, currentInfo.mCharacters.length); + if (currentInfo.mChildrenAddress != FormatSpec.NO_CHILDREN_ADDRESS) { + updateParentAddresses(buffer, currentInfo.mChildrenAddress, + newNodeAddress + written + 1, header.mFormatOptions); + } + final int suffixFlags = BinaryDictInputOutput.makeCharGroupFlags( + suffixCharacters.length > 1, + (currentInfo.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0, + 0 /* childrenAddressSize */, + (currentInfo.mFlags & FormatSpec.FLAG_HAS_SHORTCUT_TARGETS) + != 0, + (currentInfo.mFlags & FormatSpec.FLAG_HAS_BIGRAMS) != 0, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo suffixInfo = new CharGroupInfo( + newNodeAddress + written + 1, -1 /* endAddress */, suffixFlags, + suffixCharacters, currentInfo.mFrequency, newNodeAddress + 1, + currentInfo.mChildrenAddress, currentInfo.mShortcutTargets, + currentInfo.mBigrams); + written += computeGroupSize(suffixInfo, header.mFormatOptions) + 1; + + final int[] newCharacters = Arrays.copyOfRange(codePoints, wordPos + p, + codePoints.length); + final int flags = BinaryDictInputOutput.makeCharGroupFlags( + newCharacters.length > 1, isTerminal, + 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo( + newNodeAddress + written, -1 /* endAddress */, flags, + newCharacters, frequency, newNodeAddress + 1, + FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[] { suffixInfo, newInfo }); + return; + } + matched = false; + break; + } + } + + if (matched) { + if (wordPos + currentInfo.mCharacters.length == wordLen) { + // the word exists in the dictionary. + // only update group. + final int newNodeAddress = buffer.limit(); + final boolean hasMultipleChars = currentInfo.mCharacters.length > 1; + final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1, + -1 /* endAddress */, flags, currentInfo.mCharacters, frequency, + nodeParentAddress, currentInfo.mChildrenAddress, shortcuts, + bigrams); + moveCharGroup(destination, buffer, newInfo, nodeOriginAddress, address, + header.mFormatOptions); + return; + } + wordPos += currentInfo.mCharacters.length; + if (currentInfo.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { + /* + * found the prefix of the word. + * make new node and link to the node from this group. + * + * before + * ab - cd + * + * insert "abcde" + * + * after + * ab - cd - e + */ + final int newNodeAddress = buffer.limit(); + updateChildrenAddress(buffer, address, newNodeAddress, + header.mFormatOptions); + final int newGroupAddress = newNodeAddress + 1; + final boolean hasMultipleChars = (wordLen - wordPos) > 1; + final int flags = BinaryDictInputOutput.makeCharGroupFlags(hasMultipleChars, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); + final CharGroupInfo newInfo = new CharGroupInfo(newGroupAddress, -1, flags, + characters, frequency, address, FormatSpec.NO_CHILDREN_ADDRESS, + shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[] { newInfo }); + return; + } + buffer.position(currentInfo.mChildrenAddress); + foundNextGroup = true; + break; + } + } + + if (foundNextGroup) continue; + + // reached the end of the array. + final int linkAddressPosition = buffer.position(); + int nextLink = buffer.readUnsignedInt24(); + if ((nextLink & MSB24) != 0) { + nextLink = -(nextLink & SINT24_MAX); + } + if (nextLink == FormatSpec.NO_FORWARD_LINK_ADDRESS) { + /* + * expand this node. + * + * before + * ab - cd + * + * insert "abef" + * + * after + * ab - cd + * | + * - ef + */ + + // change the forward link address. + final int newNodeAddress = buffer.limit(); + buffer.position(linkAddressPosition); + writeSInt24ToBuffer(buffer, newNodeAddress); + + final int[] characters = Arrays.copyOfRange(codePoints, wordPos, wordLen); + final int flags = BinaryDictInputOutput.makeCharGroupFlags(characters.length > 1, + isTerminal, 0 /* childrenAddressSize */, hasShortcuts, hasBigrams, + isNotAWord, isBlackListEntry, header.mFormatOptions); + final CharGroupInfo newInfo = new CharGroupInfo(newNodeAddress + 1, + -1 /* endAddress */, flags, characters, frequency, nodeParentAddress, + FormatSpec.NO_CHILDREN_ADDRESS, shortcuts, bigrams); + writeNode(destination, new CharGroupInfo[]{ newInfo }); + return; + } else { + depth--; + buffer.position(nextLink); + } + } + } + + /** + * Find a word from the buffer. + * + * @param buffer the buffer representing the body of the dictionary file. + * @param word the word searched + * @return the found group + * @throws IOException + * @throws UnsupportedFormatException + */ + @UsedForTesting + public static CharGroupInfo findWordFromBuffer(final FusionDictionaryBufferInterface buffer, + final String word) throws IOException, UnsupportedFormatException { + int position = getTerminalPosition(buffer, word); + if (position != FormatSpec.NOT_VALID_WORD) { + buffer.position(0); + final FileHeader header = BinaryDictInputOutput.readHeader(buffer); + buffer.position(position); + return BinaryDictInputOutput.readCharGroup(buffer, position, header.mFormatOptions); + } + return null; + } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index b431a4da9..d1a3c7b0a 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.makedict; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; @@ -76,12 +77,12 @@ public final class BinaryDictInputOutput { @Override public int readUnsignedByte() { - return ((int)mBuffer.get()) & 0xFF; + return mBuffer.get() & 0xFF; } @Override public int readUnsignedShort() { - return ((int)mBuffer.getShort()) & 0xFFFF; + return mBuffer.getShort() & 0xFFFF; } @Override @@ -124,8 +125,7 @@ public final class BinaryDictInputOutput { /** * A class grouping utility function for our specific character encoding. */ - private static final class CharEncoding { - + static final class CharEncoding { private static final int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; private static final int MAXIMAL_ONE_BYTE_CHARACTER_VALUE = 0xFF; @@ -154,7 +154,7 @@ public final class BinaryDictInputOutput { * @param character the character code. * @return the size in binary encoded-form, either 1 or 3 bytes. */ - private static int getCharSize(final int character) { + static int getCharSize(final int character) { // See char encoding in FusionDictionary.java if (fitsOnOneByte(character)) return 1; if (FormatSpec.INVALID_CHARACTER == character) return 1; @@ -263,7 +263,7 @@ public final class BinaryDictInputOutput { * @param buffer the buffer, positioned over an encoded character. * @return the character code. */ - private static int readChar(final FusionDictionaryBufferInterface buffer) { + static int readChar(final FusionDictionaryBufferInterface buffer) { int character = buffer.readUnsignedByte(); if (!fitsOnOneByte(character)) { if (FormatSpec.GROUP_CHARACTERS_TERMINATOR == character) { @@ -277,6 +277,21 @@ public final class BinaryDictInputOutput { } /** + * Compute the binary size of the character array. + * + * If only one character, this is the size of this character. If many, it's the sum of their + * sizes + 1 byte for the terminator. + * + * @param characters the character array + * @return the size of the char array, including the terminator if any + */ + static int getGroupCharactersSize(final int[] characters) { + int size = CharEncoding.getCharArraySize(characters); + if (characters.length > 1) size += FormatSpec.GROUP_TERMINATOR_SIZE; + return size; + } + + /** * Compute the binary size of the character array in a group * * If only one character, this is the size of this character. If many, it's the sum of their @@ -286,9 +301,7 @@ public final class BinaryDictInputOutput { * @return the size of the char array, including the terminator if any */ private static int getGroupCharactersSize(final CharGroup group) { - int size = CharEncoding.getCharArraySize(group.mChars); - if (group.hasSeveralChars()) size += FormatSpec.GROUP_TERMINATOR_SIZE; - return size; + return getGroupCharactersSize(group.mChars); } /** @@ -338,7 +351,7 @@ public final class BinaryDictInputOutput { * This is known in advance and does not change according to position in the file * like address lists do. */ - private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) { + static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) { if (null == shortcutList) return 0; int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE; for (final WeightedString shortcut : shortcutList) { @@ -399,7 +412,16 @@ public final class BinaryDictInputOutput { * Helper method to check whether the group is moved. */ public static boolean isMovedGroup(final int flags, final FormatOptions options) { - return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1); + return options.mSupportsDynamicUpdate + && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_MOVED); + } + + /** + * Helper method to check whether the group is deleted. + */ + public static boolean isDeletedGroup(final int flags, final FormatOptions formatOptions) { + return formatOptions.mSupportsDynamicUpdate + && ((flags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) == FormatSpec.FLAG_IS_DELETED); } /** @@ -439,7 +461,7 @@ public final class BinaryDictInputOutput { * @param address the address * @return the byte size. */ - private static int getByteSize(final int address) { + static int getByteSize(final int address) { assert(address <= UINT24_MAX); if (!hasChildrenAddress(address)) { return 0; @@ -452,11 +474,8 @@ public final class BinaryDictInputOutput { } } - private static final int SINT8_MAX = 0x7F; - private static final int SINT16_MAX = 0x7FFF; private static final int SINT24_MAX = 0x7FFFFF; private static final int MSB8 = 0x80; - private static final int MSB16 = 0x8000; private static final int MSB24 = 0x800000; // End utility methods. @@ -721,53 +740,60 @@ public final class BinaryDictInputOutput { return 3; } - private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress, - final int childrenOffset, final FormatOptions formatOptions) { + /** + * Makes the flag value for a char group. + * + * @param hasMultipleChars whether the group has multiple chars. + * @param isTerminal whether the group is terminal. + * @param childrenAddressSize the size of a children address. + * @param hasShortcuts whether the group has shortcuts. + * @param hasBigrams whether the group has bigrams. + * @param isNotAWord whether the group is not a word. + * @param isBlackListEntry whether the group is a blacklist entry. + * @param formatOptions file format options. + * @return the flags + */ + static int makeCharGroupFlags(final boolean hasMultipleChars, final boolean isTerminal, + final int childrenAddressSize, final boolean hasShortcuts, final boolean hasBigrams, + final boolean isNotAWord, final boolean isBlackListEntry, + final FormatOptions formatOptions) { byte flags = 0; - if (group.mChars.length > 1) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; - if (group.mFrequency >= 0) { - flags |= FormatSpec.FLAG_IS_TERMINAL; - } - if (null != group.mChildren) { - final int byteSize = formatOptions.mSupportsDynamicUpdate - ? FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE : getByteSize(childrenOffset); - switch (byteSize) { - case 1: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; - break; - case 2: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; - break; - case 3: - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; - break; - default: - throw new RuntimeException("Node with a strange address"); - } - } else if (formatOptions.mSupportsDynamicUpdate) { - flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; - } - if (null != group.mShortcutTargets) { - if (DBG && 0 == group.mShortcutTargets.size()) { - throw new RuntimeException("0-sized shortcut list must be null"); - } - flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; - } - if (null != group.mBigrams) { - if (DBG && 0 == group.mBigrams.size()) { - throw new RuntimeException("0-sized bigram list must be null"); + if (hasMultipleChars) flags |= FormatSpec.FLAG_HAS_MULTIPLE_CHARS; + if (isTerminal) flags |= FormatSpec.FLAG_IS_TERMINAL; + if (formatOptions.mSupportsDynamicUpdate) { + flags |= FormatSpec.FLAG_IS_NOT_MOVED; + } else if (true) { + switch (childrenAddressSize) { + case 1: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_ONEBYTE; + break; + case 2: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_TWOBYTES; + break; + case 3: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_THREEBYTES; + break; + case 0: + flags |= FormatSpec.FLAG_GROUP_ADDRESS_TYPE_NOADDRESS; + break; + default: + throw new RuntimeException("Node with a strange address"); } - flags |= FormatSpec.FLAG_HAS_BIGRAMS; - } - if (group.mIsNotAWord) { - flags |= FormatSpec.FLAG_IS_NOT_A_WORD; - } - if (group.mIsBlacklistEntry) { - flags |= FormatSpec.FLAG_IS_BLACKLISTED; } + if (hasShortcuts) flags |= FormatSpec.FLAG_HAS_SHORTCUT_TARGETS; + if (hasBigrams) flags |= FormatSpec.FLAG_HAS_BIGRAMS; + if (isNotAWord) flags |= FormatSpec.FLAG_IS_NOT_A_WORD; + if (isBlackListEntry) flags |= FormatSpec.FLAG_IS_BLACKLISTED; return flags; } + private static byte makeCharGroupFlags(final CharGroup group, final int groupAddress, + final int childrenOffset, final FormatOptions formatOptions) { + return (byte) makeCharGroupFlags(group.mChars.length > 1, group.mFrequency >= 0, + getByteSize(childrenOffset), group.mShortcutTargets != null, group.mBigrams != null, + group.mIsNotAWord, group.mIsBlacklistEntry, formatOptions); + } + /** * Makes the flag value for a bigram. * @@ -859,7 +885,7 @@ public final class BinaryDictInputOutput { * @param frequency the frequency of the attribute, 0..15 * @return the flags */ - private static final int makeShortcutFlags(final boolean more, final int frequency) { + static final int makeShortcutFlags(final boolean more, final int frequency) { return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0) + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY); } @@ -897,6 +923,7 @@ public final class BinaryDictInputOutput { */ private static int writePlacedNode(final FusionDictionary dict, byte[] buffer, final Node node, final FormatOptions formatOptions) { + // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup int index = node.mCachedAddress; final int groupCount = node.mData.size(); @@ -1178,7 +1205,7 @@ public final class BinaryDictInputOutput { // Input methods: Read a binary dictionary to memory. // readDictionaryBinary is the public entry point for them. - private static int getChildrenAddressSize(final int optionFlags, + static int getChildrenAddressSize(final int optionFlags, final FormatOptions formatOptions) { if (formatOptions.mSupportsDynamicUpdate) return FormatSpec.SIGNED_CHILDREN_ADDRESS_SIZE; switch (optionFlags & FormatSpec.MASK_GROUP_ADDRESS_TYPE) { @@ -1194,7 +1221,7 @@ public final class BinaryDictInputOutput { } } - private static int readChildrenAddress(final FusionDictionaryBufferInterface buffer, + static int readChildrenAddress(final FusionDictionaryBufferInterface buffer, final int optionFlags, final FormatOptions options) { if (options.mSupportsDynamicUpdate) { final int address = buffer.readUnsignedInt24(); @@ -1219,7 +1246,7 @@ public final class BinaryDictInputOutput { } } - private static int readParentAddress(final FusionDictionaryBufferInterface buffer, + static int readParentAddress(final FusionDictionaryBufferInterface buffer, final FormatOptions formatOptions) { if (supportsDynamicUpdate(formatOptions)) { final int parentAddress = buffer.readUnsignedInt24(); @@ -1290,7 +1317,8 @@ public final class BinaryDictInputOutput { ArrayList<PendingAttribute> bigrams = null; if (0 != (flags & FormatSpec.FLAG_HAS_BIGRAMS)) { bigrams = new ArrayList<PendingAttribute>(); - while (true) { + int bigramCount = 0; + while (bigramCount++ < FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { final int bigramFlags = buffer.readUnsignedByte(); ++addressPointer; final int sign = 0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE) @@ -1318,6 +1346,9 @@ public final class BinaryDictInputOutput { bigramAddress)); if (0 == (bigramFlags & FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT)) break; } + if (bigramCount >= FormatSpec.MAX_BIGRAMS_IN_A_GROUP) { + MakedictLog.d("too many bigrams in a group."); + } } return new CharGroupInfo(originalGroupAddress, addressPointer, flags, characters, frequency, parentAddress, childrenAddress, shortcutTargets, bigrams); @@ -1340,7 +1371,8 @@ public final class BinaryDictInputOutput { // of this method. Since it performs direct, unbuffered random access to the file and // may be called hundreds of thousands of times, the resulting performance is not // reasonable without some kind of cache. Thus: - private static TreeMap<Integer, String> wordCache = new TreeMap<Integer, String>(); + private static TreeMap<Integer, WeightedString> wordCache = + new TreeMap<Integer, WeightedString>(); /** * Finds, as a string, the word at the address passed as an argument. * @@ -1348,15 +1380,15 @@ public final class BinaryDictInputOutput { * @param headerSize the size of the header. * @param address the address to seek. * @param formatOptions file format options. - * @return the word, as a string. + * @return the word with its frequency, as a weighted string. */ - /* packages for tests */ static String getWordAtAddress( + /* package for tests */ static WeightedString getWordAtAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions formatOptions) { - final String cachedString = wordCache.get(address); + final WeightedString cachedString = wordCache.get(address); if (null != cachedString) return cachedString; - final String result; + final WeightedString result; final int originalPointer = buffer.position(); buffer.position(address); @@ -1372,14 +1404,17 @@ public final class BinaryDictInputOutput { return result; } + // TODO: static!? This will behave erratically when used in multi-threaded code. + // We need to fix this private static int[] sGetWordBuffer = new int[FormatSpec.MAX_WORD_LENGTH]; - private static String getWordAtAddressWithParentAddress( + private static WeightedString getWordAtAddressWithParentAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions options) { final StringBuilder builder = new StringBuilder(); int currentAddress = address; int index = FormatSpec.MAX_WORD_LENGTH - 1; + int frequency = Integer.MIN_VALUE; // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) { CharGroupInfo currentInfo; @@ -1394,6 +1429,7 @@ public final class BinaryDictInputOutput { MakedictLog.d("Too many jumps - probably a bug"); } } while (isMovedGroup(currentInfo.mFlags, options)); + if (Integer.MIN_VALUE == frequency) frequency = currentInfo.mFrequency; for (int i = 0; i < currentInfo.mCharacters.length; ++i) { sGetWordBuffer[index--] = currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1]; @@ -1402,17 +1438,19 @@ public final class BinaryDictInputOutput { currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress; } - return new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1); + return new WeightedString( + new String(sGetWordBuffer, index + 1, FormatSpec.MAX_WORD_LENGTH - index - 1), + frequency); } - private static String getWordAtAddressWithoutParentAddress( + private static WeightedString getWordAtAddressWithoutParentAddress( final FusionDictionaryBufferInterface buffer, final int headerSize, final int address, final FormatOptions options) { buffer.position(headerSize); final int count = readCharGroupCount(buffer); int groupOffset = getGroupCountSize(count); final StringBuilder builder = new StringBuilder(); - String result = null; + WeightedString result = null; CharGroupInfo last = null; for (int i = count - 1; i >= 0; --i) { @@ -1420,7 +1458,7 @@ public final class BinaryDictInputOutput { groupOffset = info.mEndAddress; if (info.mOriginalAddress == address) { builder.append(new String(info.mCharacters, 0, info.mCharacters.length)); - result = builder.toString(); + result = new WeightedString(builder.toString(), info.mFrequency); break; // and return } if (hasChildrenAddress(info.mChildrenAddress)) { @@ -1481,9 +1519,11 @@ public final class BinaryDictInputOutput { if (null != info.mBigrams) { bigrams = new ArrayList<WeightedString>(); for (PendingAttribute bigram : info.mBigrams) { - final String word = getWordAtAddress( + final WeightedString word = getWordAtAddress( buffer, headerSize, bigram.mAddress, options); - bigrams.add(new WeightedString(word, bigram.mFrequency)); + final int reconstructedFrequency = + reconstructBigramFrequency(word.mFrequency, bigram.mFrequency); + bigrams.add(new WeightedString(word.mWord, reconstructedFrequency)); } } if (hasChildrenAddress(info.mChildrenAddress)) { @@ -1618,6 +1658,7 @@ public final class BinaryDictInputOutput { * @param dict an optional dictionary to add words to, or null. * @return the created (or merged) dictionary. */ + @UsedForTesting public static FusionDictionary readDictionaryBinary( final FusionDictionaryBufferInterface buffer, final FusionDictionary dict) throws IOException, UnsupportedFormatException { @@ -1655,17 +1696,24 @@ public final class BinaryDictInputOutput { } /** + * Helper method to pass a file name instead of a File object to isBinaryDictionary. + */ + public static boolean isBinaryDictionary(final String filename) { + final File file = new File(filename); + return isBinaryDictionary(file); + } + + /** * Basic test to find out whether the file is a binary dictionary or not. * * Concretely this only tests the magic number. * - * @param filename The name of the file to test. + * @param file The file to test. * @return true if it's a binary dictionary, false otherwise */ - public static boolean isBinaryDictionary(final String filename) { + public static boolean isBinaryDictionary(final File file) { FileInputStream inStream = null; try { - final File file = new File(filename); inStream = new FileInputStream(file); final ByteBuffer buffer = inStream.getChannel().map( FileChannel.MapMode.READ_ONLY, 0, file.length()); @@ -1700,8 +1748,7 @@ public final class BinaryDictInputOutput { final int bigramFrequency) { final float stepSize = (FormatSpec.MAX_TERMINAL_FREQUENCY - unigramFrequency) / (1.5f + FormatSpec.MAX_BIGRAM_FREQUENCY); - final float resultFreqFloat = (float)unigramFrequency - + stepSize * (bigramFrequency + 1.0f); + final float resultFreqFloat = unigramFrequency + stepSize * (bigramFrequency + 1.0f); return (int)resultFreqFloat; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index b3fbb9fb5..705f66414 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -59,9 +59,11 @@ public final class FormatSpec { * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES * g | ELSE - * s | is moved ? 2 bits, 11 = no - * | 01 = yes + * s | is moved ? 2 bits, 11 = no : FLAG_IS_NOT_MOVED + * | This must be the same as FLAG_GROUP_ADDRESS_TYPE_THREEBYTES + * | 01 = yes : FLAG_IS_MOVED * | the new address is stored in the same place as the parent address + * | is deleted? 10 = yes : FLAG_IS_DELETED * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS @@ -170,6 +172,7 @@ public final class FormatSpec { static final int PARENT_ADDRESS_SIZE = 3; static final int FORWARD_LINK_ADDRESS_SIZE = 3; + // These flags are used only in the static dictionary. static final int MASK_GROUP_ADDRESS_TYPE = 0xC0; static final int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00; static final int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40; @@ -183,7 +186,13 @@ public final class FormatSpec { static final int FLAG_HAS_BIGRAMS = 0x04; static final int FLAG_IS_NOT_A_WORD = 0x02; static final int FLAG_IS_BLACKLISTED = 0x01; - static final int FLAG_IS_MOVED = 0x40; + + // These flags are used only in the dynamic dictionary. + static final int MASK_MOVE_AND_DELETE_FLAG = 0xC0; + static final int FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE = 0x40; + static final int FLAG_IS_MOVED = 0x00 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE; + static final int FLAG_IS_NOT_MOVED = 0x80 | FIXED_BIT_OF_DYNAMIC_UPDATE_MOVE; + static final int FLAG_IS_DELETED = 0x80; static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80; static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40; @@ -210,10 +219,13 @@ public final class FormatSpec { static final int MAX_CHARGROUPS_FOR_ONE_BYTE_CHARGROUP_COUNT = 0x7F; // 127 static final int MAX_CHARGROUPS_IN_A_NODE = 0x7FFF; // 32767 + static final int MAX_BIGRAMS_IN_A_GROUP = 10000; static final int MAX_TERMINAL_FREQUENCY = 255; static final int MAX_BIGRAM_FREQUENCY = 15; + public static final int SHORTCUT_WHITELIST_FREQUENCY = 15; + // This option needs to be the same numeric value as the one in binary_format.h. static final int NOT_VALID_WORD = -99; static final int SIGNED_CHILDREN_ADDRESS_SIZE = 3; diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index 3193ef457..b0b3777df 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -21,6 +21,7 @@ import com.android.inputmethod.latin.Constants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; @@ -141,6 +142,33 @@ public final class FusionDictionary implements Iterable<Word> { return NOT_A_TERMINAL != mFrequency; } + public int getFrequency() { + return mFrequency; + } + + public boolean getIsNotAWord() { + return mIsNotAWord; + } + + public boolean getIsBlacklistEntry() { + return mIsBlacklistEntry; + } + + public ArrayList<WeightedString> getShortcutTargets() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mShortcutTargets) return null; + final ArrayList<WeightedString> copyOfShortcutTargets = + new ArrayList<WeightedString>(mShortcutTargets); + return copyOfShortcutTargets; + } + + public ArrayList<WeightedString> getBigrams() { + // We don't want write permission to escape outside the package, so we return a copy + if (null == mBigrams) return null; + final ArrayList<WeightedString> copyOfBigrams = new ArrayList<WeightedString>(mBigrams); + return copyOfBigrams; + } + public boolean hasSeveralChars() { assert(mChars.length > 0); return 1 < mChars.length; @@ -249,8 +277,6 @@ public final class FusionDictionary implements Iterable<Word> { /** * Options global to the dictionary. - * - * There are no options at the moment, so this class is empty. */ public static final class DictionaryOptions { public final boolean mGermanUmlautProcessing; @@ -262,6 +288,43 @@ public final class FusionDictionary implements Iterable<Word> { mGermanUmlautProcessing = germanUmlautProcessing; mFrenchLigatureProcessing = frenchLigatureProcessing; } + @Override + public String toString() { // Convenience method + return toString(0, false); + } + public String toString(final int indentCount, final boolean plumbing) { + final StringBuilder indent = new StringBuilder(); + if (plumbing) { + indent.append("H:"); + } else { + for (int i = 0; i < indentCount; ++i) { + indent.append(" "); + } + } + final StringBuilder s = new StringBuilder(); + for (final String optionKey : mAttributes.keySet()) { + s.append(indent); + s.append(optionKey); + s.append(" = "); + if ("date".equals(optionKey) && !plumbing) { + // Date needs a number of milliseconds, but the dictionary contains seconds + s.append(new Date( + 1000 * Long.parseLong(mAttributes.get(optionKey))).toString()); + } else { + s.append(mAttributes.get(optionKey)); + } + s.append("\n"); + } + if (mGermanUmlautProcessing) { + s.append(indent); + s.append("Needs German umlaut processing\n"); + } + if (mFrenchLigatureProcessing) { + s.append(indent); + s.append("Needs French ligature processing\n"); + } + return s.toString(); + } } public final DictionaryOptions mOptions; @@ -279,7 +342,7 @@ public final class FusionDictionary implements Iterable<Word> { /** * Helper method to convert a String to an int array. */ - static private int[] getCodePoints(final String word) { + static int[] getCodePoints(final String word) { // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray, // which is not visible from the makedict package. Factor this code. final char[] characters = word.toCharArray(); @@ -358,6 +421,10 @@ public final class FusionDictionary implements Iterable<Word> { if (charGroup2 == null) { add(getCodePoints(word2), 0, null, false /* isNotAWord */, false /* isBlacklistEntry */); + // The chargroup for the first word may have moved by the above insertion, + // if word1 and word2 share a common stem that happens not to have been + // a cutting point until now. In this case, we need to refresh charGroup. + charGroup = findWordInTree(mRoot, word1); } charGroup.addBigram(word2, frequency); } else { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 5a11ae534..49b98863f 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -212,7 +212,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService } } - private final ArrayList<CharSequence> mSuggestions; + private final ArrayList<String> mSuggestions; private final int[] mScores; private final String mOriginalText; private final float mSuggestionThreshold; @@ -335,7 +335,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService gatheredSuggestions = mSuggestions.toArray(EMPTY_STRING_ARRAY); final int bestScore = mScores[mLength - 1]; - final CharSequence bestSuggestion = mSuggestions.get(0); + final String bestSuggestion = mSuggestions.get(0); final float normalizedScore = BinaryDictionary.calcNormalizedScore( mOriginalText, bestSuggestion.toString(), bestScore); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 53ed4d3c3..a8f323999 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -268,7 +268,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { dictInfo.mDictionary.getSuggestions(composer, prevWord, dictInfo.mProximityInfo); for (final SuggestedWordInfo suggestion : suggestions) { - final String suggestionStr = suggestion.mWord.toString(); + final String suggestionStr = suggestion.mWord; suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, suggestionStr.length(), suggestion.mScore); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index 1fb2bbb6a..eae5d2e60 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -51,11 +51,11 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> new Dictionary(Dictionary.TYPE_MAIN) { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, - final CharSequence prevWord, final ProximityInfo proximityInfo) { + final String prevWord, final ProximityInfo proximityInfo) { return noSuggestions; } @Override - public boolean isValidWord(CharSequence word) { + public boolean isValidWord(final String word) { // This is never called. However if for some strange reason it ever gets // called, returning true is less destructive (it will not underline the // word in red). diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java index 11bb97031..6c0d79c2b 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.spellcheck; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -23,7 +24,7 @@ import com.android.inputmethod.latin.Constants; import java.util.TreeMap; public final class SpellCheckerProximityInfo { - /* public for test */ + @UsedForTesting final public static int NUL = Constants.NOT_A_CODE; // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 4e9fd1968..35d5a0067 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; @@ -66,7 +65,7 @@ public final class MoreSuggestions extends Keyboard { int pos = fromPos, rowStartPos = fromPos; final int size = Math.min(suggestions.size(), SuggestionStripView.MAX_SUGGESTIONS); while (pos < size) { - final String word = suggestions.getWord(pos).toString(); + final String word = suggestions.getWord(pos); // TODO: Should take care of text x-scaling. mWidths[pos] = (int)view.getLabelWidth(word, paint) + padding; final int numColumn = pos - rowStartPos + 1; @@ -176,11 +175,11 @@ public final class MoreSuggestions extends Keyboard { } public Builder layout(final SuggestedWords suggestions, final int fromPos, - final int maxWidth, final int minWidth, final int maxRow) { - final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard(); + final int maxWidth, final int minWidth, final int maxRow, + final Keyboard parentKeyboard) { final int xmlId = R.xml.kbd_suggestions_pane_template; - load(xmlId, keyboard.mId); - mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2; + load(xmlId, parentKeyboard.mId); + mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2; mPaneView.updateKeyboardGeometry(mParams.mDefaultRowHeight); final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow, diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index 03a2e73d1..6cdd9e2cd 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -56,17 +56,17 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP final KeyboardActionListener mSuggestionsPaneListener = new KeyboardActionListener.Adapter() { @Override - public void onPressKey(int primaryCode) { + public void onPressKey(final int primaryCode) { mListener.onPressKey(primaryCode); } @Override - public void onReleaseKey(int primaryCode, boolean withSliding) { + public void onReleaseKey(final int primaryCode, final boolean withSliding) { mListener.onReleaseKey(primaryCode, withSliding); } @Override - public void onCodeInput(int primaryCode, int x, int y) { + public void onCodeInput(final int primaryCode, final int x, final int y) { final int index = primaryCode - MoreSuggestions.SUGGESTION_CODE_BASE; if (index >= 0 && index < SuggestionStripView.MAX_SUGGESTIONS) { mListener.onCustomRequest(index); @@ -79,11 +79,12 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } }; - public MoreSuggestionsView(Context context, AttributeSet attrs) { + public MoreSuggestionsView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreSuggestionsViewStyle); } - public MoreSuggestionsView(Context context, AttributeSet attrs, int defStyle) { + public MoreSuggestionsView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); final Resources res = context.getResources(); @@ -94,7 +95,7 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { final Keyboard keyboard = getKeyboard(); if (keyboard != null) { final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight(); @@ -110,7 +111,7 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } @Override - public void setKeyboard(Keyboard keyboard) { + public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); mModalPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), -getPaddingTop()); mSlidingPanelKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), @@ -138,15 +139,16 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } @Override - public void setKeyPreviewPopupEnabled(boolean previewEnabled, int delay) { + public void setKeyPreviewPopupEnabled(final boolean previewEnabled, final int delay) { // Suggestions pane needs no pop-up key preview displayed, so we pass always false with a // delay of 0. The delay does not matter actually since the popup is not shown anyway. super.setKeyPreviewPopupEnabled(false, 0); } @Override - public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, - PopupWindow window, KeyboardActionListener listener) { + public void showMoreKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final PopupWindow window, + final KeyboardActionListener listener) { mController = controller; mListener = listener; final View container = (View)getParent(); @@ -179,12 +181,12 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP } @Override - public int translateX(int x) { + public int translateX(final int x) { return x - mOriginX; } @Override - public int translateY(int y) { + public int translateY(final int y) { return y - mOriginY; } @@ -211,7 +213,7 @@ public final class MoreSuggestionsView extends KeyboardView implements MoreKeysP }; @Override - public boolean onTouchEvent(MotionEvent me) { + public boolean onTouchEvent(final MotionEvent me) { final int action = me.getAction(); final long eventTime = me.getEventTime(); final int index = me.getActionIndex(); diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index e926fa209..e7cb97fc2 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -52,13 +52,16 @@ import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; +import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardActionListener; +import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.keyboard.ViewLayoutUtils; import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResourceUtils; @@ -74,7 +77,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick OnLongClickListener { public interface Listener { public boolean addWordToUserDictionary(String word); - public void pickSuggestionManually(int index, CharSequence word); + public void pickSuggestionManually(int index, String word); } // The maximum number of suggestions available. See {@link Suggest#mPrefMaxSuggestions}. @@ -83,7 +86,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick static final boolean DBG = LatinImeLogger.sDBG; private final ViewGroup mSuggestionsStrip; - private KeyboardView mKeyboardView; + KeyboardView mKeyboardView; private final View mMoreSuggestionsContainer; private final MoreSuggestionsView mMoreSuggestionsView; @@ -97,8 +100,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final PopupWindow mPreviewPopup; private final TextView mPreviewText; - private Listener mListener; - private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; + Listener mListener; + SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; private final SuggestionStripViewParams mParams; private static final float MIN_TEXT_XSCALE = 0.70f; @@ -108,12 +111,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private static final class UiHandler extends StaticInnerHandlerWrapper<SuggestionStripView> { private static final int MSG_HIDE_PREVIEW = 0; - public UiHandler(SuggestionStripView outerInstance) { + public UiHandler(final SuggestionStripView outerInstance) { super(outerInstance); } @Override - public void dispatchMessage(Message msg) { + public void dispatchMessage(final Message msg) { final SuggestionStripView suggestionStripView = getOuterInstance(); switch (msg.what) { case MSG_HIDE_PREVIEW: @@ -177,8 +180,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final TextView mLeftwardsArrowView; private final TextView mHintToSaveView; - public SuggestionStripViewParams(Context context, AttributeSet attrs, int defStyle, - ArrayList<TextView> words, ArrayList<View> dividers, ArrayList<TextView> infos) { + public SuggestionStripViewParams(final Context context, final AttributeSet attrs, + final int defStyle, final ArrayList<TextView> words, final ArrayList<View> dividers, + final ArrayList<TextView> infos) { mWords = words; mDividers = dividers; mInfos = infos; @@ -250,7 +254,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return mMaxMoreSuggestionsRow * mMoreSuggestionsRowHeight + mMoreSuggestionsBottomGap; } - public int setMoreSuggestionsHeight(int remainingHeight) { + public int setMoreSuggestionsHeight(final int remainingHeight) { final int currentHeight = getMoreSuggestionsHeight(); if (currentHeight <= remainingHeight) { return currentHeight; @@ -262,7 +266,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return newHeight; } - private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) { + private static Drawable getMoreSuggestionsHint(final Resources res, final float textSize, + final int color) { final Paint paint = new Paint(); paint.setAntiAlias(true); paint.setTextAlign(Align.CENTER); @@ -279,8 +284,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return new BitmapDrawable(res, buffer); } - private CharSequence getStyledSuggestionWord(SuggestedWords suggestedWords, int pos) { - final CharSequence word = suggestedWords.getWord(pos); + private CharSequence getStyledSuggestionWord(final SuggestedWords suggestedWords, + final int pos) { + final String word = suggestedWords.getWord(pos); final boolean isAutoCorrect = pos == 1 && suggestedWords.willAutoCorrect(); final boolean isTypedWordValid = pos == 0 && suggestedWords.mTypedWordValid; if (!isAutoCorrect && !isTypedWordValid) @@ -299,7 +305,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return spannedWord; } - private int getWordPosition(int index, SuggestedWords suggestedWords) { + private int getWordPosition(final int index, final SuggestedWords suggestedWords) { // TODO: This works for 3 suggestions. Revisit this algorithm when there are 5 or more // suggestions. final int centerPos = suggestedWords.willAutoCorrect() ? 1 : 0; @@ -312,7 +318,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private int getSuggestionTextColor(int index, SuggestedWords suggestedWords, int pos) { + private int getSuggestionTextColor(final int index, final SuggestedWords suggestedWords, + final int pos) { // TODO: Need to revisit this logic with bigram suggestions final boolean isSuggested = (pos != 0); @@ -331,7 +338,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick // is in slot 1. if (index == mCenterSuggestionIndex && AutoCorrection.shouldBlockAutoCorrectionBySafetyNet( - suggestedWords.getWord(1).toString(), suggestedWords.getWord(0))) { + suggestedWords.getWord(1), suggestedWords.getWord(0))) { return 0xFFFF0000; } } @@ -355,8 +362,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick params.gravity = Gravity.CENTER; } - public void layout(SuggestedWords suggestedWords, ViewGroup stripView, ViewGroup placer, - int stripWidth) { + public void layout(final SuggestedWords suggestedWords, final ViewGroup stripView, + final ViewGroup placer, final int stripWidth) { if (suggestedWords.mIsPunctuationSuggestions) { layoutPunctuationSuggestions(suggestedWords, stripView); return; @@ -402,7 +409,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick x += word.getMeasuredWidth(); if (DBG && pos < suggestedWords.size()) { - final CharSequence debugInfo = Utils.getDebugInfo(suggestedWords, pos); + final String debugInfo = Utils.getDebugInfo(suggestedWords, pos); if (debugInfo != null) { final TextView info = mInfos.get(pos); info.setText(debugInfo); @@ -418,14 +425,14 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private int getSuggestionWidth(int index, int maxWidth) { + private int getSuggestionWidth(final int index, final int maxWidth) { final int paddings = mPadding * mSuggestionsCountInStrip; final int dividers = mDividerWidth * (mSuggestionsCountInStrip - 1); final int availableWidth = maxWidth - paddings - dividers; return (int)(availableWidth * getSuggestionWeight(index)); } - private float getSuggestionWeight(int index) { + private float getSuggestionWeight(final int index) { if (index == mCenterSuggestionIndex) { return mCenterSuggestionWeight; } else { @@ -434,7 +441,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private void setupTexts(SuggestedWords suggestedWords, int countInStrip) { + private void setupTexts(final SuggestedWords suggestedWords, final int countInStrip) { mTexts.clear(); final int count = Math.min(suggestedWords.size(), countInStrip); for (int pos = 0; pos < count; pos++) { @@ -447,8 +454,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private void layoutPunctuationSuggestions(SuggestedWords suggestedWords, - ViewGroup stripView) { + private void layoutPunctuationSuggestions(final SuggestedWords suggestedWords, + final ViewGroup stripView) { final int countInStrip = Math.min(suggestedWords.size(), PUNCTUATIONS_IN_STRIP); for (int index = 0; index < countInStrip; index++) { if (index != 0) { @@ -459,7 +466,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick final TextView word = mWords.get(index); word.setEnabled(true); word.setTextColor(mColorAutoCorrect); - final CharSequence text = suggestedWords.getWord(index); + final String text = suggestedWords.getWord(index); word.setText(text); word.setTextScaleX(1.0f); word.setCompoundDrawables(null, null, null, null); @@ -469,8 +476,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mMoreSuggestionsAvailable = false; } - public void layoutAddToDictionaryHint(CharSequence word, ViewGroup stripView, - int stripWidth, CharSequence hintText, OnClickListener listener) { + public void layoutAddToDictionaryHint(final String word, final ViewGroup stripView, + final int stripWidth, final CharSequence hintText, final OnClickListener listener) { final int width = stripWidth - mDividerWidth - mPadding * 2; final TextView wordView = mWordToSaveView; @@ -511,11 +518,11 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return (CharSequence)mWordToSaveView.getTag(); } - public boolean isAddToDictionaryShowing(View v) { + public boolean isAddToDictionaryShowing(final View v) { return v == mWordToSaveView || v == mHintToSaveView || v == mLeftwardsArrowView; } - private static void setLayoutWeight(View v, float weight, int height) { + private static void setLayoutWeight(final View v, final float weight, final int height) { final ViewGroup.LayoutParams lp = v.getLayoutParams(); if (lp instanceof LinearLayout.LayoutParams) { final LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams)lp; @@ -525,7 +532,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - private static float getTextScaleX(CharSequence text, int maxWidth, TextPaint paint) { + private static float getTextScaleX(final CharSequence text, final int maxWidth, + final TextPaint paint) { paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); if (width <= maxWidth) { @@ -534,8 +542,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return maxWidth / (float)width; } - private static CharSequence getEllipsizedText(CharSequence text, int maxWidth, - TextPaint paint) { + private static CharSequence getEllipsizedText(final CharSequence text, final int maxWidth, + final TextPaint paint) { if (text == null) return null; paint.setTextScaleX(1.0f); final int width = getTextWidth(text, paint); @@ -556,7 +564,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return ellipsized; } - private static int getTextWidth(CharSequence text, TextPaint paint) { + private static int getTextWidth(final CharSequence text, final TextPaint paint) { if (TextUtils.isEmpty(text)) return 0; final Typeface savedTypeface = paint.getTypeface(); paint.setTypeface(getTextTypeface(text)); @@ -571,7 +579,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return width; } - private static Typeface getTextTypeface(CharSequence text) { + private static Typeface getTextTypeface(final CharSequence text) { if (!(text instanceof SpannableString)) return Typeface.DEFAULT; @@ -593,11 +601,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick * @param context * @param attrs */ - public SuggestionStripView(Context context, AttributeSet attrs) { + public SuggestionStripView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.suggestionStripViewStyle); } - public SuggestionStripView(Context context, AttributeSet attrs, int defStyle) { + public SuggestionStripView(final Context context, final AttributeSet attrs, + final int defStyle) { super(context, attrs, defStyle); final LayoutInflater inflater = LayoutInflater.from(context); @@ -658,15 +667,12 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick * A connection back to the input method. * @param listener */ - public void setListener(Listener listener, View inputView) { + public void setListener(final Listener listener, final View inputView) { mListener = listener; mKeyboardView = (KeyboardView)inputView.findViewById(R.id.keyboard_view); } - public void setSuggestions(SuggestedWords suggestedWords) { - if (suggestedWords == null) - return; - + public void setSuggestions(final SuggestedWords suggestedWords) { clear(); mSuggestedWords = suggestedWords; mParams.layout(mSuggestedWords, mSuggestionsStrip, this, getWidth()); @@ -675,7 +681,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } } - public int setMoreSuggestionsHeight(int remainingHeight) { + public int setMoreSuggestionsHeight(final int remainingHeight) { return mParams.setMoreSuggestionsHeight(remainingHeight); } @@ -684,7 +690,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick && mParams.isAddToDictionaryShowing(mSuggestionsStrip.getChildAt(0)); } - public void showAddToDictionaryHint(CharSequence word, CharSequence hintText) { + public void showAddToDictionaryHint(final String word, final CharSequence hintText) { clear(); mParams.layoutAddToDictionaryHint(word, mSuggestionsStrip, getWidth(), hintText, this); } @@ -708,16 +714,16 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick dismissMoreSuggestions(); } - private void hidePreview() { + void hidePreview() { mPreviewPopup.dismiss(); } private final KeyboardActionListener mMoreSuggestionsListener = new KeyboardActionListener.Adapter() { @Override - public boolean onCustomRequest(int requestCode) { + public boolean onCustomRequest(final int requestCode) { final int index = requestCode; - final CharSequence word = mSuggestedWords.getWord(index); + final String word = mSuggestedWords.getWord(index); mListener.pickSuggestionManually(index, word); dismissMoreSuggestions(); return true; @@ -737,7 +743,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } }; - private boolean dismissMoreSuggestions() { + boolean dismissMoreSuggestions() { if (mMoreSuggestionsWindow.isShowing()) { mMoreSuggestionsWindow.dismiss(); return true; @@ -746,41 +752,43 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } @Override - public boolean onLongClick(View view) { + public boolean onLongClick(final View view) { + KeyboardSwitcher.getInstance().hapticAndAudioFeedback(Constants.NOT_A_CODE); return showMoreSuggestions(); } - private boolean showMoreSuggestions() { + boolean showMoreSuggestions() { + final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard(); + if (parentKeyboard == null) { + return false; + } final SuggestionStripViewParams params = mParams; - if (params.mMoreSuggestionsAvailable) { - final int stripWidth = getWidth(); - final View container = mMoreSuggestionsContainer; - final int maxWidth = stripWidth - container.getPaddingLeft() - - container.getPaddingRight(); - final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; - builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, - (int)(maxWidth * params.mMinMoreSuggestionsWidth), - params.getMaxMoreSuggestionsRow()); - mMoreSuggestionsView.setKeyboard(builder.build()); - container.measure( - ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; - final int pointX = stripWidth / 2; - final int pointY = -params.mMoreSuggestionsBottomGap; - moreKeysPanel.showMoreKeysPanel( - this, mMoreSuggestionsController, pointX, pointY, - mMoreSuggestionsWindow, mMoreSuggestionsListener); - mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; - mOriginX = mLastX; - mOriginY = mLastY; - mKeyboardView.dimEntireKeyboard(true); - for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { - mWords.get(i).setPressed(false); - } - return true; + if (!params.mMoreSuggestionsAvailable) { + return false; } - return false; + final int stripWidth = getWidth(); + final View container = mMoreSuggestionsContainer; + final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight(); + final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder; + builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth, + (int)(maxWidth * params.mMinMoreSuggestionsWidth), + params.getMaxMoreSuggestionsRow(), parentKeyboard); + mMoreSuggestionsView.setKeyboard(builder.build()); + container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView; + final int pointX = stripWidth / 2; + final int pointY = -params.mMoreSuggestionsBottomGap; + moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY, + mMoreSuggestionsWindow, mMoreSuggestionsListener); + mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; + mOriginX = mLastX; + mOriginY = mLastY; + mKeyboardView.dimEntireKeyboard(true); + for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { + mWords.get(i).setPressed(false); + } + return true; } // Working variables for onLongClick and dispatchTouchEvent. @@ -807,7 +815,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick }; @Override - public boolean dispatchTouchEvent(MotionEvent me) { + public boolean dispatchTouchEvent(final MotionEvent me) { if (!mMoreSuggestionsWindow.isShowing() || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) { mLastX = (int)me.getX(); @@ -849,7 +857,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick } @Override - public void onClick(View view) { + public void onClick(final View view) { if (mParams.isAddToDictionaryShowing(view)) { mListener.addWordToUserDictionary(mParams.getAddToDictionaryWord().toString()); clear(); @@ -863,7 +871,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick if (index >= mSuggestedWords.size()) return; - final CharSequence word = mSuggestedWords.getWord(index); + final String word = mSuggestedWords.getWord(index); mListener.pickSuggestionManually(index, word); } diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 763fd6e00..6295abe8c 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -870,7 +870,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final long time = SystemClock.uptimeMillis(); final ResearchLogger researchLogger = getInstance(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), x, y + Constants.printableCode(scrubDigitFromCodePoint(code)), x, y }; researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_ONCODEINPUT, values); if (Character.isDigit(code)) { @@ -1006,7 +1006,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang }; public static void latinIME_sendKeyCodePoint(final int code) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)) + Constants.printableCode(scrubDigitFromCodePoint(code)) }; final ResearchLogger researchLogger = getInstance(); researchLogger.enqueuePotentiallyPrivateEvent(EVENTKEYS_LATINIME_SENDKEYCODEPOINT, values); @@ -1092,7 +1092,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (key != null) { String outputText = key.getOutputText(); final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null + Constants.printableCode(scrubDigitFromCodePoint(code)), outputText == null ? null : scrubDigitsFromString(outputText.toString()), x, y, ignoreModifierKey, altersCode, key.isEnabled() }; @@ -1109,7 +1109,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang final boolean withSliding, final boolean ignoreModifierKey) { if (key != null) { final Object[] values = { - Keyboard.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, + Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, ignoreModifierKey, key.isEnabled() }; getInstance().enqueuePotentiallyPrivateEvent( diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java index eab465aa2..98491bd23 100644 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ b/java/src/com/android/inputmethod/research/Statistics.java @@ -16,7 +16,7 @@ package com.android.inputmethod.research; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; public class Statistics { // Number of characters entered during a typing session @@ -104,7 +104,7 @@ public class Statistics { public void recordChar(int codePoint, long time) { final long delta = time - mLastTapTime; - if (codePoint == Keyboard.CODE_DELETE) { + if (codePoint == Constants.CODE_DELETE) { mDeleteKeyCount++; if (delta < MIN_DELETION_INTERMISSION) { if (mIsLastKeyDeleteKey) { diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 567648f7a..e87577e06 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -28,7 +28,14 @@ LATIN_IME_SRC_FULLPATH_DIR := $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR) LOCAL_C_INCLUDES += $(LATIN_IME_SRC_FULLPATH_DIR) $(LATIN_IME_SRC_FULLPATH_DIR)/gesture -LOCAL_CFLAGS += -Werror -Wall +LOCAL_CFLAGS += -Werror -Wall -Wextra -Weffc++ -Wformat=2 -Wcast-qual -Wcast-align \ + -Wwrite-strings -Wfloat-equal -Wpointer-arith -Winit-self -Wredundant-decls -Wno-system-headers + +ifeq ($(TARGET_ARCH), arm) +ifneq ($(TARGET_GCC_VERSION), 4.7) +LOCAL_CFLAGS += -Winline +endif # TARGET_GCC_VERSION +endif # TARGET_ARCH # To suppress compiler warnings for unused variables/functions used for debug features etc. LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function @@ -41,7 +48,6 @@ LATIN_IME_JNI_SRC_FILES := \ LATIN_IME_CORE_SRC_FILES := \ additional_proximity_chars.cpp \ - basechars.cpp \ bigram_dictionary.cpp \ char_utils.cpp \ correction.cpp \ @@ -50,6 +56,7 @@ LATIN_IME_CORE_SRC_FILES := \ proximity_info.cpp \ proximity_info_state.cpp \ unigram_dictionary.cpp \ + words_priority_queue.cpp \ gesture/gesture_decoder_wrapper.cpp \ gesture/incremental_decoder_wrapper.cpp diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp index 560b3a533..2423bb53b 100644 --- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp +++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "LatinIME: jni: ProximityInfo" #include "com_android_inputmethod_keyboard_ProximityInfo.h" +#include "defines.h" #include "jni.h" #include "jni_common.h" #include "proximity_info.h" @@ -41,7 +42,7 @@ static void latinime_Keyboard_release(JNIEnv *env, jobject object, jlong proximi delete pi; } -static JNINativeMethod sKeyboardMethods[] = { +static JNINativeMethod sMethods[] = { {"setProximityInfoNative", "(Ljava/lang/String;IIIIII[II[I[I[I[I[I[F[F[F)J", reinterpret_cast<void *>(latinime_Keyboard_setProximityInfo)}, {"releaseProximityInfoNative", "(J)V", reinterpret_cast<void *>(latinime_Keyboard_release)} @@ -49,7 +50,6 @@ static JNINativeMethod sKeyboardMethods[] = { int register_ProximityInfo(JNIEnv *env) { const char *const kClassPathName = "com/android/inputmethod/keyboard/ProximityInfo"; - return registerNativeMethods(env, kClassPathName, sKeyboardMethods, - sizeof(sKeyboardMethods) / sizeof(sKeyboardMethods[0])); + return registerNativeMethods(env, kClassPathName, sMethods, NELEMS(sMethods)); } } // namespace latinime diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index dd2513f9f..c0b858e52 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -43,10 +43,8 @@ class ProximityInfo; static void releaseDictBuf(const void *dictBuf, const size_t length, const int fd); -static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, - jstring sourceDir, jlong dictOffset, jlong dictSize, - jint typedLetterMultiplier, jint fullWordMultiplier, jint maxWordLength, jint maxWords, - jint maxPredictions) { +static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, jstring sourceDir, + jlong dictOffset, jlong dictSize, jint maxWordLength, jint maxWords, jint maxPredictions) { PROF_OPEN; PROF_START(66); const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir); @@ -71,7 +69,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, adjust = static_cast<int>(dictOffset) % pagesize; int adjDictOffset = static_cast<int>(dictOffset) - adjust; int adjDictSize = static_cast<int>(dictSize) + adjust; - dictBuf = mmap(0, sizeof(char) * adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset); + dictBuf = mmap(0, adjDictSize, PROT_READ, MAP_PRIVATE, fd, adjDictOffset); if (dictBuf == MAP_FAILED) { AKLOGE("DICT: Can't mmap dictionary. errno=%d", errno); return 0; @@ -85,7 +83,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, AKLOGE("DICT: Can't fopen sourceDir. sourceDirChars=%s errno=%d", sourceDirChars, errno); return 0; } - dictBuf = malloc(sizeof(char) * dictSize); + dictBuf = malloc(dictSize); if (!dictBuf) { AKLOGE("DICT: Can't allocate memory region for dictionary. errno=%d", errno); return 0; @@ -95,7 +93,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, AKLOGE("DICT: Failure in fseek. ret=%d errno=%d", ret, errno); return 0; } - ret = fread(dictBuf, sizeof(char) * dictSize, 1, file); + ret = fread(dictBuf, dictSize, 1, file); if (ret != 1) { AKLOGE("DICT: Failure in fread. ret=%d errno=%d", ret, errno); return 0; @@ -120,8 +118,8 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, releaseDictBuf(dictBuf, 0, 0); #endif // USE_MMAP_FOR_DICTIONARY } else { - dictionary = new Dictionary(dictBuf, static_cast<int>(dictSize), fd, adjust, - typedLetterMultiplier, fullWordMultiplier, maxWordLength, maxWords, maxPredictions); + dictionary = new Dictionary(dictBuf, static_cast<int>(dictSize), fd, adjust, maxWordLength, + maxWords, maxPredictions); } PROF_END(66); PROF_CLOSE; @@ -133,7 +131,7 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, jintArray yCoordinatesArray, jintArray timesArray, jintArray pointerIdsArray, jintArray inputCodePointsArray, jint arraySize, jint commitPoint, jboolean isGesture, jintArray prevWordCodePointsForBigrams, jboolean useFullEditDistance, - jcharArray outputCharsArray, jintArray scoresArray, jintArray spaceIndicesArray, + jintArray outputCodePointsArray, jintArray scoresArray, jintArray spaceIndicesArray, jintArray outputTypesArray) { Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict); if (!dictionary) return 0; @@ -163,16 +161,15 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, } // Output values - // TODO: Should be "outputCodePointsLength" and "int outputCodePoints[]" - const jsize outputCharsLength = env->GetArrayLength(outputCharsArray); - unsigned short outputChars[outputCharsLength]; + const jsize outputCodePointsLength = env->GetArrayLength(outputCodePointsArray); + int outputCodePoints[outputCodePointsLength]; const jsize scoresLength = env->GetArrayLength(scoresArray); int scores[scoresLength]; const jsize spaceIndicesLength = env->GetArrayLength(spaceIndicesArray); int spaceIndices[spaceIndicesLength]; const jsize outputTypesLength = env->GetArrayLength(outputTypesArray); int outputTypes[outputTypesLength]; - memset(outputChars, 0, sizeof(outputChars)); + memset(outputCodePoints, 0, sizeof(outputCodePoints)); memset(scores, 0, sizeof(scores)); memset(spaceIndices, 0, sizeof(spaceIndices)); memset(outputTypes, 0, sizeof(outputTypes)); @@ -181,16 +178,15 @@ static int latinime_BinaryDictionary_getSuggestions(JNIEnv *env, jobject object, if (isGesture || arraySize > 0) { count = dictionary->getSuggestions(pInfo, traverseSession, xCoordinates, yCoordinates, times, pointerIds, inputCodePoints, arraySize, prevWordCodePoints, - prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, outputChars, - scores, spaceIndices, outputTypes); + prevWordCodePointsLength, commitPoint, isGesture, useFullEditDistance, + outputCodePoints, scores, spaceIndices, outputTypes); } else { count = dictionary->getBigrams(prevWordCodePoints, prevWordCodePointsLength, - inputCodePoints, arraySize, outputChars, scores, outputTypes); + inputCodePoints, arraySize, outputCodePoints, scores, outputTypes); } // Copy back the output values - // TODO: Should be SetIntArrayRegion() - env->SetCharArrayRegion(outputCharsArray, 0, outputCharsLength, outputChars); + env->SetIntArrayRegion(outputCodePointsArray, 0, outputCodePointsLength, outputCodePoints); env->SetIntArrayRegion(scoresArray, 0, scoresLength, scores); env->SetIntArrayRegion(spaceIndicesArray, 0, spaceIndicesLength, spaceIndices); env->SetIntArrayRegion(outputTypesArray, 0, outputTypesLength, outputTypes); @@ -222,29 +218,27 @@ static jboolean latinime_BinaryDictionary_isValidBigram(JNIEnv *env, jobject obj } static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jobject object, - jcharArray before, jcharArray after, jint score) { + jintArray before, jintArray after, jint score) { jsize beforeLength = env->GetArrayLength(before); jsize afterLength = env->GetArrayLength(after); - jchar beforeChars[beforeLength]; - jchar afterChars[afterLength]; - env->GetCharArrayRegion(before, 0, beforeLength, beforeChars); - env->GetCharArrayRegion(after, 0, afterLength, afterChars); - return Correction::RankingAlgorithm::calcNormalizedScore( - static_cast<unsigned short *>(beforeChars), beforeLength, - static_cast<unsigned short *>(afterChars), afterLength, score); + int beforeCodePoints[beforeLength]; + int afterCodePoints[afterLength]; + env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints); + env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints); + return Correction::RankingAlgorithm::calcNormalizedScore(beforeCodePoints, beforeLength, + afterCodePoints, afterLength, score); } -static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object, - jcharArray before, jcharArray after) { +static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jobject object, jintArray before, + jintArray after) { jsize beforeLength = env->GetArrayLength(before); jsize afterLength = env->GetArrayLength(after); - jchar beforeChars[beforeLength]; - jchar afterChars[afterLength]; - env->GetCharArrayRegion(before, 0, beforeLength, beforeChars); - env->GetCharArrayRegion(after, 0, afterLength, afterChars); - return Correction::RankingAlgorithm::editDistance( - static_cast<unsigned short *>(beforeChars), beforeLength, - static_cast<unsigned short *>(afterChars), afterLength); + int beforeCodePoints[beforeLength]; + int afterCodePoints[afterLength]; + env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints); + env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints); + return Correction::RankingAlgorithm::editDistance(beforeCodePoints, beforeLength, + afterCodePoints, afterLength); } static void latinime_BinaryDictionary_close(JNIEnv *env, jobject object, jlong dict) { @@ -277,24 +271,23 @@ static void releaseDictBuf(const void *dictBuf, const size_t length, const int f } static JNINativeMethod sMethods[] = { - {"openNative", "(Ljava/lang/String;JJIIIII)J", + {"openNative", "(Ljava/lang/String;JJIII)J", reinterpret_cast<void *>(latinime_BinaryDictionary_open)}, {"closeNative", "(J)V", reinterpret_cast<void *>(latinime_BinaryDictionary_close)}, - {"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[C[I[I[I)I", + {"getSuggestionsNative", "(JJJ[I[I[I[I[IIIZ[IZ[I[I[I[I)I", reinterpret_cast<void *>(latinime_BinaryDictionary_getSuggestions)}, {"getFrequencyNative", "(J[I)I", reinterpret_cast<void *>(latinime_BinaryDictionary_getFrequency)}, {"isValidBigramNative", "(J[I[I)Z", reinterpret_cast<void *>(latinime_BinaryDictionary_isValidBigram)}, - {"calcNormalizedScoreNative", "([C[CI)F", + {"calcNormalizedScoreNative", "([I[II)F", reinterpret_cast<void *>(latinime_BinaryDictionary_calcNormalizedScore)}, - {"editDistanceNative", "([C[C)I", + {"editDistanceNative", "([I[I)I", reinterpret_cast<void *>(latinime_BinaryDictionary_editDistance)} }; int register_BinaryDictionary(JNIEnv *env) { const char *const kClassPathName = "com/android/inputmethod/latin/BinaryDictionary"; - return registerNativeMethods(env, kClassPathName, sMethods, - sizeof(sMethods) / sizeof(sMethods[0])); + return registerNativeMethods(env, kClassPathName, sMethods, NELEMS(sMethods)); } } // namespace latinime diff --git a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp index 5d405f117..7bb8dc56e 100644 --- a/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp +++ b/native/jni/com_android_inputmethod_latin_DicTraverseSession.cpp @@ -17,6 +17,7 @@ #define LOG_TAG "LatinIME: jni: Session" #include "com_android_inputmethod_latin_DicTraverseSession.h" +#include "defines.h" #include "dic_traverse_wrapper.h" #include "jni.h" #include "jni_common.h" @@ -57,7 +58,6 @@ static JNINativeMethod sMethods[] = { int register_DicTraverseSession(JNIEnv *env) { const char *const kClassPathName = "com/android/inputmethod/latin/DicTraverseSession"; - return registerNativeMethods(env, kClassPathName, sMethods, - sizeof(sMethods) / sizeof(sMethods[0])); + return registerNativeMethods(env, kClassPathName, sMethods, NELEMS(sMethods)); } } // namespace latinime diff --git a/native/jni/src/additional_proximity_chars.h b/native/jni/src/additional_proximity_chars.h index d420c4664..0333c2dbd 100644 --- a/native/jni/src/additional_proximity_chars.h +++ b/native/jni/src/additional_proximity_chars.h @@ -39,7 +39,7 @@ class AdditionalProximityChars { static const int EN_US_ADDITIONAL_U_SIZE = 4; static const int32_t EN_US_ADDITIONAL_U[]; - static bool isEnLocale(const char *localeStr) { + AK_FORCE_INLINE static bool isEnLocale(const char *localeStr) { const size_t LOCALE_EN_US_SIZE = strlen(LOCALE_EN_US); return localeStr && strlen(localeStr) >= LOCALE_EN_US_SIZE && strncmp(localeStr, LOCALE_EN_US, LOCALE_EN_US_SIZE) == 0; diff --git a/native/jni/src/basechars.cpp b/native/jni/src/basechars.cpp deleted file mode 100644 index d97311ed6..000000000 --- a/native/jni/src/basechars.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <stdint.h> - -#include "char_utils.h" - -namespace latinime { - -/* - * Table mapping most combined Latin, Greek, and Cyrillic characters - * to their base characters. If c is in range, BASE_CHARS[c] == c - * if c is not a combined character, or the base character if it - * is combined. - */ -const uint16_t BASE_CHARS[BASE_CHARS_SIZE] = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, - 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, - 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, - 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, - 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, - 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, - 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, - 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, - 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, - 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, - 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, - 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, - 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, - 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, - 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, - 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f, - 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, - 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f, - 0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, - 0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020, - 0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7, - 0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf, - 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043, - 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, - 0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7, - 0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f - // Manually changed df to 73 - 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063, - 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, - 0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7, - 0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f - 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063, - 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064, - 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065, - 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067, - 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127, - 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, - 0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b, - 0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, - 0x006c, 0x004c, 0x006c, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e, - 0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f, - 0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072, - 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073, - 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167, - 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, - 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079, - 0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073, - 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187, - 0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f, - 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, - 0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f, - 0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7, - 0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055, - 0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7, - 0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf, - 0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c, - 0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049, - 0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc, - 0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4, - 0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067, - 0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292, - 0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7, - 0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8, - 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065, - 0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f, - 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075, - 0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068, - 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061, - 0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f, - 0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, - 0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f, - 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, - 0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f, - 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, - 0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f, - 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, - 0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f, - 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, - 0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f, - 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, - 0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f, - 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, - 0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f, - 0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7, - 0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af, - 0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077, - 0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf, - 0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7, - 0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf, - 0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7, - 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df, - 0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7, - 0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef, - 0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7, - 0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff, - 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, - 0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f, - 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, - 0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f, - 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, - 0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f, - 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, - 0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f, - 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347, - 0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f, - 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, - 0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f, - 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, - 0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f, - 0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377, - 0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f, - 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7, - 0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9, - 0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, - 0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f, - 0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7, - 0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9, - 0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7, - 0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf, - 0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7, - 0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf, - 0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7, - 0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, - 0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7, - 0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef, - 0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7, - 0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff, - 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, - 0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f, - 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, - 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, - 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, - 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, - 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, - 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f, - 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, - 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, - 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, - 0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f, - 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, - 0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f, - 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475, - 0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f, - 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, - 0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f, - 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, - 0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f, - 0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7, - 0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af, - 0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7, - 0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf, - 0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7, - 0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf, - 0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435, - 0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437, - 0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e, - 0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443, - 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7, - 0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff, -}; -// generated with: -// cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }' -} // namespace latinime diff --git a/native/jni/src/bigram_dictionary.cpp b/native/jni/src/bigram_dictionary.cpp index dade4f16b..f89dd1615 100644 --- a/native/jni/src/bigram_dictionary.cpp +++ b/native/jni/src/bigram_dictionary.cpp @@ -36,13 +36,13 @@ BigramDictionary::BigramDictionary(const unsigned char *dict, int maxWordLength, BigramDictionary::~BigramDictionary() { } -bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequency, - int *bigramFreq, unsigned short *bigramChars, int *outputTypes) const { +bool BigramDictionary::addWordBigram(int *word, int length, int frequency, int *bigramFreq, + int *bigramCodePoints, int *outputTypes) const { word[length] = 0; if (DEBUG_DICT) { #ifdef FLAG_DBG char s[length + 1]; - for (int i = 0; i <= length; i++) s[i] = word[i]; + for (int i = 0; i <= length; i++) s[i] = static_cast<char>(word[i]); AKLOGI("Bigram: Found word = %s, freq = %d :", s, frequency); #endif } @@ -51,7 +51,8 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ int insertAt = 0; while (insertAt < MAX_PREDICTIONS) { if (frequency > bigramFreq[insertAt] || (bigramFreq[insertAt] == frequency - && length < Dictionary::wideStrLen(bigramChars + insertAt * MAX_WORD_LENGTH))) { + && length < Dictionary::wideStrLen( + bigramCodePoints + insertAt * MAX_WORD_LENGTH))) { break; } insertAt++; @@ -65,10 +66,10 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramFreq[0])); bigramFreq[insertAt] = frequency; outputTypes[insertAt] = Dictionary::KIND_PREDICTION; - memmove(bigramChars + (insertAt + 1) * MAX_WORD_LENGTH, - bigramChars + insertAt * MAX_WORD_LENGTH, - (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramChars[0]) * MAX_WORD_LENGTH); - unsigned short *dest = bigramChars + insertAt * MAX_WORD_LENGTH; + memmove(bigramCodePoints + (insertAt + 1) * MAX_WORD_LENGTH, + bigramCodePoints + insertAt * MAX_WORD_LENGTH, + (MAX_PREDICTIONS - insertAt - 1) * sizeof(bigramCodePoints[0]) * MAX_WORD_LENGTH); + int *dest = bigramCodePoints + insertAt * MAX_WORD_LENGTH; while (length--) { *dest++ = *word++; } @@ -86,7 +87,7 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ * prevWordLength: its length. * inputCodes: what user typed, in the same format as for UnigramDictionary::getSuggestions. * codesSize: the size of the codes array. - * bigramChars: an array for output, at the same format as outwords for getSuggestions. + * bigramCodePoints: an array for output, at the same format as outwords for getSuggestions. * bigramFreq: an array to output frequencies. * outputTypes: an array to output types. * This method returns the number of bigrams this word has, for backward compatibility. @@ -97,8 +98,8 @@ bool BigramDictionary::addWordBigram(unsigned short *word, int length, int frequ * and the bigrams are used to boost unigram result scores, it makes little sense to * reduce their scope to the ones that match the first letter. */ -int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, int *inputCodes, - int codesSize, unsigned short *bigramChars, int *bigramFreq, int *outputTypes) const { +int BigramDictionary::getBigrams(const int *prevWord, int prevWordLength, int *inputCodes, + int codesSize, int *bigramCodePoints, int *bigramFreq, int *outputTypes) const { // TODO: remove unused arguments, and refrain from storing stuff in members of this class // TODO: have "in" arguments before "out" ones, and make out args explicit in the name @@ -117,7 +118,7 @@ int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, in int bigramCount = 0; do { bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); - uint16_t bigramBuffer[MAX_WORD_LENGTH]; + int bigramBuffer[MAX_WORD_LENGTH]; int unigramFreq = 0; const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags, &pos); @@ -134,7 +135,7 @@ int BigramDictionary::getBigrams(const int32_t *prevWord, int prevWordLength, in // here, but it can't get too bad. const int frequency = BinaryFormat::computeFrequencyForBigram(unigramFreq, bigramFreqTemp); - if (addWordBigram(bigramBuffer, length, frequency, bigramFreq, bigramChars, + if (addWordBigram(bigramBuffer, length, frequency, bigramFreq, bigramCodePoints, outputTypes)) { ++bigramCount; } @@ -190,12 +191,12 @@ void BigramDictionary::fillBigramAddressToFrequencyMapAndFilter(const int32_t *p } while (0 != (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags)); } -bool BigramDictionary::checkFirstCharacter(unsigned short *word, int *inputCodes) const { +bool BigramDictionary::checkFirstCharacter(int *word, int *inputCodes) const { // Checks whether this word starts with same character or neighboring characters of // what user typed. int maxAlt = MAX_ALTERNATIVES; - const unsigned short firstBaseChar = toBaseLowerCase(*word); + const int firstBaseChar = toBaseLowerCase(*word); while (maxAlt > 0) { if (toBaseLowerCase(*inputCodes) == firstBaseChar) { return true; diff --git a/native/jni/src/bigram_dictionary.h b/native/jni/src/bigram_dictionary.h index 5f11ae822..150192de2 100644 --- a/native/jni/src/bigram_dictionary.h +++ b/native/jni/src/bigram_dictionary.h @@ -27,23 +27,23 @@ namespace latinime { class BigramDictionary { public: BigramDictionary(const unsigned char *dict, int maxWordLength, int maxPredictions); - int getBigrams(const int32_t *word, int length, int *inputCodes, int codesSize, - unsigned short *outWords, int *frequencies, int *outputTypes) const; - void fillBigramAddressToFrequencyMapAndFilter(const int32_t *prevWord, const int prevWordLength, + int getBigrams(const int *word, int length, int *inputCodes, int codesSize, int *outWords, + int *frequencies, int *outputTypes) const; + void fillBigramAddressToFrequencyMapAndFilter(const int *prevWord, const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const; - bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const; + bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const; ~BigramDictionary(); private: DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictionary); - bool addWordBigram(unsigned short *word, int length, int frequency, - int *bigramFreq, unsigned short *bigramChars, int *outputTypes) const; + bool addWordBigram(int *word, int length, int frequency, int *bigramFreq, int *bigramCodePoints, + int *outputTypes) const; int getBigramAddress(int *pos, bool advance); int getBigramFreq(int *pos); void searchForTerminalNode(int addressLookingFor, int frequency); bool getFirstBitOfByte(int *pos) { return (DICT[*pos] & 0x80) > 0; } bool getSecondBitOfByte(int *pos) { return (DICT[*pos] & 0x40) > 0; } - bool checkFirstCharacter(unsigned short *word, int *inputCodes) const; - int getBigramListPositionForWord(const int32_t *prevWord, const int prevWordLength, + bool checkFirstCharacter(int *word, int *inputCodes) const; + int getBigramListPositionForWord(const int *prevWord, const int prevWordLength, const bool forceLowerCaseSearch) const; const unsigned char *DICT; diff --git a/native/jni/src/binary_format.h b/native/jni/src/binary_format.h index eec52e323..9a8c315f7 100644 --- a/native/jni/src/binary_format.h +++ b/native/jni/src/binary_format.h @@ -28,10 +28,6 @@ class BinaryFormat { public: // Mask and flags for children address type selection. static const int MASK_GROUP_ADDRESS_TYPE = 0xC0; - static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00; - static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40; - static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80; - static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0; // Flag for single/multiple char group static const int FLAG_HAS_MULTIPLE_CHARS = 0x20; @@ -61,36 +57,21 @@ class BinaryFormat { // Mask and flags for attribute address type selection. static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30; - static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10; - static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20; - static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30; - const static int UNKNOWN_FORMAT = -1; - // Originally, format version 1 had a 16-bit magic number, then the version number `01' - // then options that must be 0. Hence the first 32-bits of the format are always as follow - // and it's okay to consider them a magic number as a whole. - const static uint32_t FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100; - const static unsigned int FORMAT_VERSION_1_HEADER_SIZE = 5; - // The versions of Latin IME that only handle format version 1 only test for the magic - // number, so we had to change it so that version 2 files would be rejected by older - // implementations. On this occasion, we made the magic number 32 bits long. - const static uint32_t FORMAT_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE; - - const static int CHARACTER_ARRAY_TERMINATOR_SIZE = 1; - const static int SHORTCUT_LIST_SIZE_SIZE = 2; + static const int UNKNOWN_FORMAT = -1; + static const int SHORTCUT_LIST_SIZE_SIZE = 2; static int detectFormat(const uint8_t *const dict); static unsigned int getHeaderSize(const uint8_t *const dict); static unsigned int getFlags(const uint8_t *const dict); static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos); static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos); - static int32_t getCodePointAndForwardPointer(const uint8_t *const dict, int *pos); + static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos); static int readFrequencyWithoutMovingPointer(const uint8_t *const dict, const int pos); static int skipOtherCharacters(const uint8_t *const dict, const int pos); static int skipChildrenPosition(const uint8_t flags, const int pos); static int skipFrequency(const uint8_t flags, const int pos); static int skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos); - static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos); static int skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags, const int pos); static int readChildrenPosition(const uint8_t *const dict, const uint8_t flags, const int pos); @@ -98,10 +79,10 @@ class BinaryFormat { static int getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags, int *pos); static int getAttributeFrequencyFromFlags(const int flags); - static int getTerminalPosition(const uint8_t *const root, const int32_t *const inWord, + static int getTerminalPosition(const uint8_t *const root, const int *const inWord, const int length, const bool forceLowerCaseSearch); static int getWordAtAddress(const uint8_t *const root, const int address, const int maxDepth, - uint16_t *outWord, int *outUnigramFrequency); + int *outWord, int *outUnigramFrequency); static int computeFrequencyForBigram(const int unigramFreq, const int bigramFreq); static int getProbability(const int position, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, const int unigramFreq); @@ -113,17 +94,37 @@ class BinaryFormat { REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1, REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4 }; - const static unsigned int NO_FLAGS = 0; private: DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat); - const static int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; - const static int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F; - const static int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2; + static const int FLAG_GROUP_ADDRESS_TYPE_NOADDRESS = 0x00; + static const int FLAG_GROUP_ADDRESS_TYPE_ONEBYTE = 0x40; + static const int FLAG_GROUP_ADDRESS_TYPE_TWOBYTES = 0x80; + static const int FLAG_GROUP_ADDRESS_TYPE_THREEBYTES = 0xC0; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE = 0x10; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20; + static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30; + + // Originally, format version 1 had a 16-bit magic number, then the version number `01' + // then options that must be 0. Hence the first 32-bits of the format are always as follow + // and it's okay to consider them a magic number as a whole. + static const uint32_t FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100; + static const unsigned int FORMAT_VERSION_1_HEADER_SIZE = 5; + // The versions of Latin IME that only handle format version 1 only test for the magic + // number, so we had to change it so that version 2 files would be rejected by older + // implementations. On this occasion, we made the magic number 32 bits long. + static const uint32_t FORMAT_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE; + + static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1; + static const int32_t MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; + static const int32_t CHARACTER_ARRAY_TERMINATOR = 0x1F; + static const int MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE = 2; + static const unsigned int NO_FLAGS = 0; static int skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos); + static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos); }; -inline int BinaryFormat::detectFormat(const uint8_t *const dict) { +AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict) { // The magic number is stored big-endian. const uint32_t magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3]; switch (magicNumber) { @@ -148,7 +149,7 @@ inline int BinaryFormat::detectFormat(const uint8_t *const dict) { inline unsigned int BinaryFormat::getFlags(const uint8_t *const dict) { switch (detectFormat(dict)) { case 1: - return NO_FLAGS; + return NO_FLAGS; // TODO: NO_FLAGS is unused anywhere else? default: return (dict[6] << 8) + dict[7]; } @@ -166,7 +167,8 @@ inline unsigned int BinaryFormat::getHeaderSize(const uint8_t *const dict) { } } -inline int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos) { +AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict, + int *pos) { const int msb = dict[(*pos)++]; if (msb < 0x80) return msb; return ((msb & 0x7F) << 8) | dict[(*pos)++]; @@ -176,17 +178,18 @@ inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict return dict[(*pos)++]; } -inline int32_t BinaryFormat::getCodePointAndForwardPointer(const uint8_t *const dict, int *pos) { +AK_FORCE_INLINE int BinaryFormat::getCodePointAndForwardPointer(const uint8_t *const dict, + int *pos) { const int origin = *pos; - const int32_t codePoint = dict[origin]; + const int codePoint = dict[origin]; if (codePoint < MINIMAL_ONE_BYTE_CHARACTER_VALUE) { if (codePoint == CHARACTER_ARRAY_TERMINATOR) { *pos = origin + 1; return NOT_A_CODE_POINT; } else { *pos = origin + 3; - const int32_t char_1 = codePoint << 16; - const int32_t char_2 = char_1 + (dict[origin + 1] << 8); + const int char_1 = codePoint << 16; + const int char_2 = char_1 + (dict[origin + 1] << 8); return char_2 + dict[origin + 2]; } } else { @@ -200,9 +203,9 @@ inline int BinaryFormat::readFrequencyWithoutMovingPointer(const uint8_t *const return dict[pos]; } -inline int BinaryFormat::skipOtherCharacters(const uint8_t *const dict, const int pos) { +AK_FORCE_INLINE int BinaryFormat::skipOtherCharacters(const uint8_t *const dict, const int pos) { int currentPos = pos; - int32_t character = dict[currentPos++]; + int character = dict[currentPos++]; while (CHARACTER_ARRAY_TERMINATOR != character) { if (character < MINIMAL_ONE_BYTE_CHARACTER_VALUE) { currentPos += MULTIPLE_BYTE_CHARACTER_ADDITIONAL_SIZE; @@ -226,7 +229,7 @@ static inline int attributeAddressSize(const uint8_t flags) { */ } -static inline int skipExistingBigrams(const uint8_t *const dict, const int pos) { +static AK_FORCE_INLINE int skipExistingBigrams(const uint8_t *const dict, const int pos) { int currentPos = pos; uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(dict, ¤tPos); while (flags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT) { @@ -255,7 +258,7 @@ inline int BinaryFormat::skipFrequency(const uint8_t flags, const int pos) { return FLAG_IS_TERMINAL & flags ? pos + 1 : pos; } -inline int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t flags, +AK_FORCE_INLINE int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t flags, const int pos) { if (FLAG_HAS_SHORTCUT_TARGETS & flags) { return pos + shortcutByteSize(dict, pos); @@ -264,7 +267,7 @@ inline int BinaryFormat::skipShortcuts(const uint8_t *const dict, const uint8_t } } -inline int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t flags, +AK_FORCE_INLINE int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos) { if (FLAG_HAS_BIGRAMS & flags) { return skipExistingBigrams(dict, pos); @@ -273,7 +276,7 @@ inline int BinaryFormat::skipBigrams(const uint8_t *const dict, const uint8_t fl } } -inline int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint8_t flags, +AK_FORCE_INLINE int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) { // This function skips all attributes: shortcuts and bigrams. int newPos = pos; @@ -282,7 +285,7 @@ inline int BinaryFormat::skipAllAttributes(const uint8_t *const dict, const uint return newPos; } -inline int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict, +AK_FORCE_INLINE int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict, const uint8_t flags, const int pos) { int currentPos = pos; currentPos = skipChildrenPosition(flags, currentPos); @@ -290,8 +293,8 @@ inline int BinaryFormat::skipChildrenPosAndAttributes(const uint8_t *const dict, return currentPos; } -inline int BinaryFormat::readChildrenPosition(const uint8_t *const dict, const uint8_t flags, - const int pos) { +AK_FORCE_INLINE int BinaryFormat::readChildrenPosition(const uint8_t *const dict, + const uint8_t flags, const int pos) { int offset = 0; switch (MASK_GROUP_ADDRESS_TYPE & flags) { case FLAG_GROUP_ADDRESS_TYPE_ONEBYTE: @@ -318,7 +321,7 @@ inline bool BinaryFormat::hasChildrenInFlags(const uint8_t flags) { return (FLAG_GROUP_ADDRESS_TYPE_NOADDRESS != (MASK_GROUP_ADDRESS_TYPE & flags)); } -inline int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *const dict, +AK_FORCE_INLINE int BinaryFormat::getAttributeAddressAndForwardPointer(const uint8_t *const dict, const uint8_t flags, int *pos) { int offset = 0; const int origin = *pos; @@ -352,8 +355,8 @@ inline int BinaryFormat::getAttributeFrequencyFromFlags(const int flags) { // This function gets the byte position of the last chargroup of the exact matching word in the // dictionary. If no match is found, it returns NOT_VALID_WORD. -inline int BinaryFormat::getTerminalPosition(const uint8_t *const root, - const int32_t *const inWord, const int length, const bool forceLowerCaseSearch) { +AK_FORCE_INLINE int BinaryFormat::getTerminalPosition(const uint8_t *const root, + const int *const inWord, const int length, const bool forceLowerCaseSearch) { int pos = 0; int wordPos = 0; @@ -362,14 +365,14 @@ inline int BinaryFormat::getTerminalPosition(const uint8_t *const root, // there was no match (or we would have found it). if (wordPos >= length) return NOT_VALID_WORD; int charGroupCount = BinaryFormat::getGroupCountAndForwardPointer(root, &pos); - const int32_t wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos]; + const int wChar = forceLowerCaseSearch ? toLowerCase(inWord[wordPos]) : inWord[wordPos]; while (true) { // If there are no more character groups in this node, it means we could not // find a matching character for this depth, therefore there is no match. if (0 >= charGroupCount) return NOT_VALID_WORD; const int charGroupPos = pos; const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); - int32_t character = BinaryFormat::getCodePointAndForwardPointer(root, &pos); + int character = BinaryFormat::getCodePointAndForwardPointer(root, &pos); if (character == wChar) { // This is the correct node. Only one character group may start with the same // char within a node, so either we found our match in this node, or there is @@ -438,8 +441,8 @@ inline int BinaryFormat::getTerminalPosition(const uint8_t *const root, * outUnigramFrequency: a pointer to an int to write the frequency into. * Return value : the length of the word, of 0 if the word was not found. */ -inline int BinaryFormat::getWordAtAddress(const uint8_t *const root, const int address, - const int maxDepth, uint16_t *outWord, int *outUnigramFrequency) { +AK_FORCE_INLINE int BinaryFormat::getWordAtAddress(const uint8_t *const root, const int address, + const int maxDepth, int *outWord, int *outUnigramFrequency) { int pos = 0; int wordPos = 0; @@ -457,13 +460,13 @@ inline int BinaryFormat::getWordAtAddress(const uint8_t *const root, const int a --charGroupCount) { const int startPos = pos; const uint8_t flags = getFlagsAndForwardPointer(root, &pos); - const int32_t character = getCodePointAndForwardPointer(root, &pos); + const int character = getCodePointAndForwardPointer(root, &pos); if (address == startPos) { // We found the address. Copy the rest of the word in the buffer and return // the length. outWord[wordPos] = character; if (FLAG_HAS_MULTIPLE_CHARS & flags) { - int32_t nextChar = getCodePointAndForwardPointer(root, &pos); + int nextChar = getCodePointAndForwardPointer(root, &pos); // We count chars in order to avoid infinite loops if the file is broken or // if there is some other bug int charCount = maxDepth; @@ -522,13 +525,12 @@ inline int BinaryFormat::getWordAtAddress(const uint8_t *const root, const int a if (0 != lastCandidateGroupPos) { const uint8_t lastFlags = getFlagsAndForwardPointer(root, &lastCandidateGroupPos); - const int32_t lastChar = + const int lastChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos); // We copy all the characters in this group to the buffer outWord[wordPos] = lastChar; if (FLAG_HAS_MULTIPLE_CHARS & lastFlags) { - int32_t nextChar = - getCodePointAndForwardPointer(root, &lastCandidateGroupPos); + int nextChar = getCodePointAndForwardPointer(root, &lastCandidateGroupPos); int charCount = maxDepth; while (-1 != nextChar && --charCount > 0) { outWord[++wordPos] = nextChar; diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp index d0547a982..ede1155a7 100644 --- a/native/jni/src/char_utils.cpp +++ b/native/jni/src/char_utils.cpp @@ -17,6 +17,7 @@ #include <cstdlib> #include "char_utils.h" +#include "defines.h" namespace latinime { @@ -33,7 +34,7 @@ struct LatinCapitalSmallPair { // // unsigned short c, cc, ccc, ccc2; // for (c = 0; c < 0xFFFF ; c++) { -// if (c < sizeof(BASE_CHARS) / sizeof(BASE_CHARS[0])) { +// if (c < NELEMS(BASE_CHARS)) { // cc = BASE_CHARS[c]; // } else { // cc = c; @@ -83,6 +84,7 @@ struct LatinCapitalSmallPair { // 3. Update the latin_tolower() function above with SORTED_CHAR_MAP. Enable // the assert(ccc == ccc2) above and confirm the function exits successfully. // +// TODO: Regenerate this map by using the updated BASE_CHARS table in this file. static const struct LatinCapitalSmallPair SORTED_CHAR_MAP[] = { { 0x00C4, 0x00E4 }, // LATIN CAPITAL LETTER A WITH DIAERESIS { 0x00C5, 0x00E5 }, // LATIN CAPITAL LETTER A WITH RING ABOVE @@ -894,9 +896,190 @@ static int compare_pair_capital(const void *a, const void *b) { unsigned short latin_tolower(const unsigned short c) { struct LatinCapitalSmallPair *p = static_cast<struct LatinCapitalSmallPair *>(bsearch(&c, SORTED_CHAR_MAP, - sizeof(SORTED_CHAR_MAP) / sizeof(SORTED_CHAR_MAP[0]), - sizeof(SORTED_CHAR_MAP[0]), - compare_pair_capital)); + NELEMS(SORTED_CHAR_MAP), sizeof(SORTED_CHAR_MAP[0]), compare_pair_capital)); return p ? p->small : c; } + +/* + * Table mapping most combined Latin, Greek, and Cyrillic characters + * to their base characters. If c is in range, BASE_CHARS[c] == c + * if c is not a combined character, or the base character if it + * is combined. + * + * Generated with: + * cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; \ + * $base[hex($foo[0])] = hex($foo[5]);} \ + * for ($i = 0; $i < 0x500; $i += 8) { printf("/" . "* U+%04X *" . "/ ", $i); \ + * for ($j = $i; $j < $i + 8; $j++) { \ + * printf("0x%04X, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }' + */ +const unsigned short BASE_CHARS[BASE_CHARS_SIZE] = { + /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F, + /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087, + /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F, + /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097, + /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F, + /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020, + /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7, + /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF, + /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043, + /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049, + /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7, + /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073, + // U+00D8: Manually changed from 00D8 to 004F + // U+00DF: Manually changed from 00DF to 0073 + /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063, + /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069, + /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7, + /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079, + // U+00F8: Manually changed from 00F8 to 006F + /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063, + /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064, + /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065, + /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067, + /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127, + /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, + /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B, + /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, + /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E, + // U+0141: Manually changed from 0141 to 004C + // U+0142: Manually changed from 0142 to 006C + /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F, + /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072, + /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073, + /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167, + /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, + /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079, + /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073, + /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187, + /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F, + /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197, + /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F, + /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7, + /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055, + /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7, + /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF, + /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C, + /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049, + /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x00DC, 0x00FC, 0x00DC, + /* U+01D8 */ 0x00FC, 0x00DC, 0x00FC, 0x00DC, 0x00FC, 0x01DD, 0x00C4, 0x00E4, + /* U+01E0 */ 0x0226, 0x0227, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067, + /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x01EA, 0x01EB, 0x01B7, 0x0292, + /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7, + /* U+01F8 */ 0x004E, 0x006E, 0x00C5, 0x00E5, 0x00C6, 0x00E6, 0x00D8, 0x00F8, + /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065, + /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F, + /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075, + /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068, + /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061, + /* U+0228 */ 0x0045, 0x0065, 0x00D6, 0x00F6, 0x00D5, 0x00F5, 0x004F, 0x006F, + /* U+0230 */ 0x022E, 0x022F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237, + /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F, + /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247, + /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F, + /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257, + /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F, + /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267, + /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F, + /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277, + /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F, + /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287, + /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F, + /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297, + /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F, + /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7, + /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF, + /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077, + /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF, + /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7, + /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF, + /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7, + /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF, + /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7, + /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF, + /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7, + /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF, + /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307, + /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F, + /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317, + /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F, + /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327, + /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F, + /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337, + /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F, + /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347, + /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F, + /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357, + /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F, + /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367, + /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F, + /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377, + /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F, + /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7, + /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9, + /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, + /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9, + /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF, + /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7, + /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF, + /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7, + /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF, + /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7, + /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF, + /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406, + /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F, + /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + // U+0419: Manually changed from 0418 to 0419 + /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + /* U+0428 */ 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + // U+0439: Manually changed from 0438 to 0439 + /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + /* U+0448 */ 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456, + /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F, + /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, + /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F, + /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475, + /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F, + /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, + /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F, + /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497, + /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F, + /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7, + /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF, + /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7, + /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF, + /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7, + /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF, + /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435, + /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437, + /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E, + /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443, + /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7, + /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF, +}; } // namespace latinime diff --git a/native/jni/src/char_utils.h b/native/jni/src/char_utils.h index 9008e364c..c632b79b8 100644 --- a/native/jni/src/char_utils.h +++ b/native/jni/src/char_utils.h @@ -18,22 +18,23 @@ #define LATINIME_CHAR_UTILS_H #include <cctype> -#include <stdint.h> + +#include "defines.h" namespace latinime { -inline static bool isAsciiUpper(unsigned short c) { +inline static bool isAsciiUpper(int c) { // Note: isupper(...) reports false positives for some Cyrillic characters, causing them to // be incorrectly lower-cased using toAsciiLower(...) rather than latin_tolower(...). return (c >= 'A' && c <= 'Z'); } -inline static unsigned short toAsciiLower(unsigned short c) { +inline static int toAsciiLower(int c) { return c - 'A' + 'a'; } -inline static bool isAscii(unsigned short c) { - return isascii(static_cast<int>(c)) != 0; +inline static bool isAscii(int c) { + return isascii(c) != 0; } unsigned short latin_tolower(const unsigned short c); @@ -44,33 +45,32 @@ unsigned short latin_tolower(const unsigned short c); * if c is not a combined character, or the base character if it * is combined. */ - static const int BASE_CHARS_SIZE = 0x0500; -extern const uint16_t BASE_CHARS[BASE_CHARS_SIZE]; +extern const unsigned short BASE_CHARS[BASE_CHARS_SIZE]; -inline static unsigned short toBaseChar(unsigned short c) { +inline static int toBaseCodePoint(int c) { if (c < BASE_CHARS_SIZE) { - return BASE_CHARS[c]; + return static_cast<int>(BASE_CHARS[c]); } return c; } -inline static unsigned short toLowerCase(const unsigned short c) { +AK_FORCE_INLINE static int toLowerCase(const int c) { if (isAsciiUpper(c)) { return toAsciiLower(c); } else if (isAscii(c)) { return c; } - return latin_tolower(c); + return static_cast<int>(latin_tolower(static_cast<unsigned short>(c))); } -inline static unsigned short toBaseLowerCase(const unsigned short c) { - return toLowerCase(toBaseChar(c)); +AK_FORCE_INLINE static int toBaseLowerCase(const int c) { + return toLowerCase(toBaseCodePoint(c)); } -inline static bool isSkippableChar(const uint16_t character) { +inline static bool isSkippableCodePoint(const int codePoint) { // TODO: Do not hardcode here - return character == '\'' || character == '-'; + return codePoint == KEYCODE_SINGLE_QUOTE || codePoint == KEYCODE_HYPHEN_MINUS; } } // namespace latinime diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp index 524abe9a1..50f33fe23 100644 --- a/native/jni/src/correction.cpp +++ b/native/jni/src/correction.cpp @@ -60,29 +60,6 @@ inline static void dumpEditDistance10ForDebug(int *editDistanceTable, } } -inline static void calcEditDistanceOneStep(int *editDistanceTable, const unsigned short *input, - const int inputSize, const unsigned short *output, const int outputLength) { - // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched. - // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j]. - // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated, - // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize]. - int *const current = editDistanceTable + outputLength * (inputSize + 1); - const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1); - const int *const prevprev = - outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0; - current[0] = outputLength; - const uint32_t co = toBaseLowerCase(output[outputLength - 1]); - const uint32_t prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0; - for (int i = 1; i <= inputSize; ++i) { - const uint32_t ci = toBaseLowerCase(input[i - 1]); - const uint16_t cost = (ci == co) ? 0 : 1; - current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost)); - if (i >= 2 && prevprev && ci == prevCO && co == toBaseLowerCase(input[i - 2])) { - current[i] = min(current[i], prevprev[i - 2] + 1); - } - } -} - inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth, const int outputLength, const int inputSize) { if (DEBUG_EDIT_DISTANCE) { @@ -91,16 +68,6 @@ inline static int getCurrentEditDistance(int *editDistanceTable, const int editD return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize]; } -////////////////////// -// inline functions // -////////////////////// -static const char SINGLE_QUOTE = '\''; - -inline bool Correction::isSingleQuote(const unsigned short c) { - const unsigned short userTypedChar = mProximityInfoState.getPrimaryCharAt(mInputIndex); - return (c == SINGLE_QUOTE && userTypedChar != SINGLE_QUOTE); -} - //////////////// // Correction // //////////////// @@ -162,31 +129,20 @@ bool Correction::sameAsTyped() { } int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, - const int wordCount, const bool isSpaceProximity, const unsigned short *word) { + const int wordCount, const bool isSpaceProximity, const int *word) { return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray, wordCount, this, isSpaceProximity, word); } -int Correction::getFinalProbability(const int probability, unsigned short **word, int *wordLength) { +int Correction::getFinalProbability(const int probability, int **word, int *wordLength) { return getFinalProbabilityInternal(probability, word, wordLength, mInputSize); } -int Correction::getFinalProbabilityForSubQueue(const int probability, unsigned short **word, - int *wordLength, const int inputSize) { +int Correction::getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength, + const int inputSize) { return getFinalProbabilityInternal(probability, word, wordLength, inputSize); } -int Correction::getFinalProbabilityInternal(const int probability, unsigned short **word, - int *wordLength, const int inputSize) { - const int outputIndex = mTerminalOutputIndex; - const int inputIndex = mTerminalInputIndex; - *wordLength = outputIndex + 1; - *word = mWord; - int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability( - inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize); - return finalProbability; -} - bool Correction::initProcessState(const int outputIndex) { if (mCorrectionStates[outputIndex].mChildCount <= 0) { return false; @@ -230,42 +186,6 @@ int Correction::getInputIndex() const { return mInputIndex; } -void Correction::incrementInputIndex() { - ++mInputIndex; -} - -void Correction::incrementOutputIndex() { - ++mOutputIndex; - mCorrectionStates[mOutputIndex].mParentIndex = mCorrectionStates[mOutputIndex - 1].mParentIndex; - mCorrectionStates[mOutputIndex].mChildCount = mCorrectionStates[mOutputIndex - 1].mChildCount; - mCorrectionStates[mOutputIndex].mSiblingPos = mCorrectionStates[mOutputIndex - 1].mSiblingPos; - mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex; - mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes; - - mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount; - mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount; - mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount; - mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount; - mCorrectionStates[mOutputIndex].mSkippedCount = mSkippedCount; - - mCorrectionStates[mOutputIndex].mSkipPos = mSkipPos; - mCorrectionStates[mOutputIndex].mTransposedPos = mTransposedPos; - mCorrectionStates[mOutputIndex].mExcessivePos = mExcessivePos; - - mCorrectionStates[mOutputIndex].mLastCharExceeded = mLastCharExceeded; - - mCorrectionStates[mOutputIndex].mMatching = mMatching; - mCorrectionStates[mOutputIndex].mProximityMatching = mProximityMatching; - mCorrectionStates[mOutputIndex].mAdditionalProximityMatching = mAdditionalProximityMatching; - mCorrectionStates[mOutputIndex].mTransposing = mTransposing; - mCorrectionStates[mOutputIndex].mExceeding = mExceeding; - mCorrectionStates[mOutputIndex].mSkipping = mSkipping; -} - -void Correction::startToTraverseAllNodes() { - mNeedsToTraverseAllNodes = true; -} - bool Correction::needsToPrune() const { // TODO: use edit distance here return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance @@ -273,44 +193,15 @@ bool Correction::needsToPrune() const { || (!mDoAutoCompletion && (mOutputIndex > mInputSize)); } -void Correction::addCharToCurrentWord(const int32_t c) { - mWord[mOutputIndex] = c; - const unsigned short *primaryInputWord = mProximityInfoState.getPrimaryInputWord(); - calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize, - mWord, mOutputIndex + 1); -} - -Correction::CorrectionType Correction::processSkipChar( - const int32_t c, const bool isTerminal, const bool inputIndexIncremented) { - addCharToCurrentWord(c); - mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0); - mTerminalOutputIndex = mOutputIndex; - if (mNeedsToTraverseAllNodes && isTerminal) { - incrementOutputIndex(); - return TRAVERSE_ALL_ON_TERMINAL; - } else { - incrementOutputIndex(); - return TRAVERSE_ALL_NOT_ON_TERMINAL; - } -} - -Correction::CorrectionType Correction::processUnrelatedCorrectionType() { - // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType - mTerminalInputIndex = mInputIndex; - mTerminalOutputIndex = mOutputIndex; - return UNRELATED; -} - -inline bool isEquivalentChar(ProximityType type) { +inline static bool isEquivalentChar(ProximityType type) { return type == EQUIVALENT_CHAR; } -inline bool isProximityCharOrEquivalentChar(ProximityType type) { +inline static bool isProximityCharOrEquivalentChar(ProximityType type) { return type == EQUIVALENT_CHAR || type == NEAR_PROXIMITY_CHAR; } -Correction::CorrectionType Correction::processCharAndCalcState( - const int32_t c, const bool isTerminal) { +Correction::CorrectionType Correction::processCharAndCalcState(const int c, const bool isTerminal) { const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount); if (correctionCount > mMaxErrors) { return processUnrelatedCorrectionType(); @@ -628,10 +519,10 @@ Correction::CorrectionType Correction::processCharAndCalcState( } } -inline static int getQuoteCount(const unsigned short *word, const int length) { +inline static int getQuoteCount(const int *word, const int length) { int quoteCount = 0; for (int i = 0; i < length; ++i) { - if (word[i] == SINGLE_QUOTE) { + if (word[i] == KEYCODE_SINGLE_QUOTE) { ++quoteCount; } } @@ -639,7 +530,7 @@ inline static int getQuoteCount(const unsigned short *word, const int length) { } inline static bool isUpperCase(unsigned short c) { - return isAsciiUpper(toBaseChar(c)); + return isAsciiUpper(toBaseCodePoint(c)); } ////////////////////// @@ -672,7 +563,7 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex // TODO: use mExcessiveCount const int matchCount = inputSize - correction->mProximityCount - excessiveCount; - const unsigned short *word = correction->mWord; + const int *word = correction->mWord; const bool skipped = skippedCount > 0; const int quoteDiffCount = max(0, getQuoteCount(word, outputLength) @@ -911,7 +802,7 @@ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex /* static */ int Correction::RankingAlgorithm::calcFreqForSplitMultipleWords( const int *freqArray, const int *wordLengthArray, const int wordCount, - const Correction *correction, const bool isSpaceProximity, const unsigned short *word) { + const Correction *correction, const bool isSpaceProximity, const int *word) { const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER; bool firstCapitalizedWordDemotion = false; @@ -1040,9 +931,8 @@ int Correction::RankingAlgorithm::calcFreqForSplitMultipleWords( } /* Damerau-Levenshtein distance */ -inline static int editDistanceInternal( - int *editDistanceTable, const unsigned short *before, - const int beforeLength, const unsigned short *after, const int afterLength) { +inline static int editDistanceInternal(int *editDistanceTable, const int *before, + const int beforeLength, const int *after, const int afterLength) { // dp[li][lo] dp[a][b] = dp[ a * lo + b] int *dp = editDistanceTable; const int li = beforeLength + 1; @@ -1056,9 +946,9 @@ inline static int editDistanceInternal( for (int i = 0; i < li - 1; ++i) { for (int j = 0; j < lo - 1; ++j) { - const uint32_t ci = toBaseLowerCase(before[i]); - const uint32_t co = toBaseLowerCase(after[j]); - const uint16_t cost = (ci == co) ? 0 : 1; + const int ci = toBaseLowerCase(before[i]); + const int co = toBaseLowerCase(after[j]); + const int cost = (ci == co) ? 0 : 1; dp[(i + 1) * lo + (j + 1)] = min(dp[i * lo + (j + 1)] + 1, min(dp[(i + 1) * lo + j] + 1, dp[i * lo + j] + cost)); if (i > 0 && j > 0 && ci == toBaseLowerCase(after[j - 1]) @@ -1080,8 +970,8 @@ inline static int editDistanceInternal( return dp[li * lo - 1]; } -int Correction::RankingAlgorithm::editDistance(const unsigned short *before, - const int beforeLength, const unsigned short *after, const int afterLength) { +int Correction::RankingAlgorithm::editDistance(const int *before, const int beforeLength, + const int *after, const int afterLength) { int table[(beforeLength + 1) * (afterLength + 1)]; return editDistanceInternal(table, before, beforeLength, after, afterLength); } @@ -1109,16 +999,15 @@ int Correction::RankingAlgorithm::editDistance(const unsigned short *before, // So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2. /* static */ -float Correction::RankingAlgorithm::calcNormalizedScore(const unsigned short *before, - const int beforeLength, const unsigned short *after, const int afterLength, - const int score) { +float Correction::RankingAlgorithm::calcNormalizedScore(const int *before, const int beforeLength, + const int *after, const int afterLength, const int score) { if (0 == beforeLength || 0 == afterLength) { return 0; } const int distance = editDistance(before, beforeLength, after, afterLength); int spaceCount = 0; for (int i = 0; i < afterLength; ++i) { - if (after[i] == CODE_SPACE) { + if (after[i] == KEYCODE_SPACE) { ++spaceCount; } } diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h index f016d5453..912cd838e 100644 --- a/native/jni/src/correction.h +++ b/native/jni/src/correction.h @@ -56,7 +56,8 @@ class Correction { // No need to initialize it explicitly here. } - virtual ~Correction() {} + // Non virtual inline destructor -- never inherit this class + ~Correction() {} void resetCorrection(); void initCorrection( const ProximityInfo *pi, const int inputSize, const int maxWordLength); @@ -78,14 +79,13 @@ class Correction { return ++mTotalTraverseCount; } - int getFreqForSplitMultipleWords( - const int *freqArray, const int *wordLengthArray, const int wordCount, - const bool isSpaceProximity, const unsigned short *word); - int getFinalProbability(const int probability, unsigned short **word, int *wordLength); - int getFinalProbabilityForSubQueue(const int probability, unsigned short **word, - int *wordLength, const int inputSize); + int getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, + const int wordCount, const bool isSpaceProximity, const int *word); + int getFinalProbability(const int probability, int **word, int *wordLength); + int getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength, + const int inputSize); - CorrectionType processCharAndCalcState(const int32_t c, const bool isTerminal); + CorrectionType processCharAndCalcState(const int c, const bool isTerminal); ///////////////////////// // Tree helper methods @@ -110,29 +110,28 @@ class Correction { const int inputSize); static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, const int wordCount, const Correction *correction, const bool isSpaceProximity, - const unsigned short *word); - static float calcNormalizedScore(const unsigned short *before, const int beforeLength, - const unsigned short *after, const int afterLength, const int score); - static int editDistance(const unsigned short *before, - const int beforeLength, const unsigned short *after, const int afterLength); + const int *word); + static float calcNormalizedScore(const int *before, const int beforeLength, + const int *after, const int afterLength, const int score); + static int editDistance(const int *before, const int beforeLength, const int *after, + const int afterLength); private: - static const int CODE_SPACE = ' '; static const int MAX_INITIAL_SCORE = 255; }; // proximity info state - void initInputParams(const ProximityInfo *proximityInfo, const int32_t *inputCodes, + void initInputParams(const ProximityInfo *proximityInfo, const int *inputCodes, const int inputSize, const int *xCoordinates, const int *yCoordinates) { mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH, proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false); } - const unsigned short *getPrimaryInputWord() const { + const int *getPrimaryInputWord() const { return mProximityInfoState.getPrimaryInputWord(); } - unsigned short getPrimaryCharAt(const int index) const { - return mProximityInfoState.getPrimaryCharAt(index); + int getPrimaryCodePointAt(const int index) const { + return mProximityInfoState.getPrimaryCodePointAt(index); } private: @@ -147,7 +146,7 @@ class Correction { } static const int TWO_31ST_DIV_2 = S_INT_MAX / 2; - inline static void multiplyIntCapped(const int multiplier, int *base) { + AK_FORCE_INLINE static void multiplyIntCapped(const int multiplier, int *base) { const int temp = *base; if (temp != S_INT_MAX) { // Branch if multiplier == 2 for the optimization @@ -170,7 +169,7 @@ class Correction { } } - inline static int powerIntCapped(const int base, const int n) { + AK_FORCE_INLINE static int powerIntCapped(const int base, const int n) { if (n <= 0) return 1; if (base == 2) { return n < 31 ? 1 << n : S_INT_MAX; @@ -181,7 +180,7 @@ class Correction { } } - inline static void multiplyRate(const int rate, int *freq) { + AK_FORCE_INLINE static void multiplyRate(const int rate, int *freq) { if (*freq != S_INT_MAX) { if (*freq > 1000000) { *freq /= 100; @@ -215,13 +214,13 @@ class Correction { inline void incrementInputIndex(); inline void incrementOutputIndex(); inline void startToTraverseAllNodes(); - inline bool isSingleQuote(const unsigned short c); - inline CorrectionType processSkipChar( - const int32_t c, const bool isTerminal, const bool inputIndexIncremented); + inline bool isSingleQuote(const int c); + inline CorrectionType processSkipChar(const int c, const bool isTerminal, + const bool inputIndexIncremented); inline CorrectionType processUnrelatedCorrectionType(); - inline void addCharToCurrentWord(const int32_t c); - inline int getFinalProbabilityInternal(const int probability, unsigned short **word, - int *wordLength, const int inputSize); + inline void addCharToCurrentWord(const int c); + inline int getFinalProbabilityInternal(const int probability, int **word, int *wordLength, + const int inputSize); static const int TYPED_LETTER_MULTIPLIER = 2; static const int FULL_WORD_MULTIPLIER = 2; @@ -241,7 +240,7 @@ class Correction { uint8_t mTotalTraverseCount; // The following arrays are state buffer. - unsigned short mWord[MAX_WORD_LENGTH_INTERNAL]; + int mWord[MAX_WORD_LENGTH_INTERNAL]; int mDistances[MAX_WORD_LENGTH_INTERNAL]; // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N. @@ -275,5 +274,109 @@ class Correction { bool mSkipping; ProximityInfoState mProximityInfoState; }; + +inline void Correction::incrementInputIndex() { + ++mInputIndex; +} + +AK_FORCE_INLINE void Correction::incrementOutputIndex() { + ++mOutputIndex; + mCorrectionStates[mOutputIndex].mParentIndex = mCorrectionStates[mOutputIndex - 1].mParentIndex; + mCorrectionStates[mOutputIndex].mChildCount = mCorrectionStates[mOutputIndex - 1].mChildCount; + mCorrectionStates[mOutputIndex].mSiblingPos = mCorrectionStates[mOutputIndex - 1].mSiblingPos; + mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex; + mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes; + + mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount; + mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount; + mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount; + mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount; + mCorrectionStates[mOutputIndex].mSkippedCount = mSkippedCount; + + mCorrectionStates[mOutputIndex].mSkipPos = mSkipPos; + mCorrectionStates[mOutputIndex].mTransposedPos = mTransposedPos; + mCorrectionStates[mOutputIndex].mExcessivePos = mExcessivePos; + + mCorrectionStates[mOutputIndex].mLastCharExceeded = mLastCharExceeded; + + mCorrectionStates[mOutputIndex].mMatching = mMatching; + mCorrectionStates[mOutputIndex].mProximityMatching = mProximityMatching; + mCorrectionStates[mOutputIndex].mAdditionalProximityMatching = mAdditionalProximityMatching; + mCorrectionStates[mOutputIndex].mTransposing = mTransposing; + mCorrectionStates[mOutputIndex].mExceeding = mExceeding; + mCorrectionStates[mOutputIndex].mSkipping = mSkipping; +} + +inline void Correction::startToTraverseAllNodes() { + mNeedsToTraverseAllNodes = true; +} + +inline bool Correction::isSingleQuote(const int c) { + const int userTypedChar = mProximityInfoState.getPrimaryCodePointAt(mInputIndex); + return (c == KEYCODE_SINGLE_QUOTE && userTypedChar != KEYCODE_SINGLE_QUOTE); +} + +AK_FORCE_INLINE Correction::CorrectionType Correction::processSkipChar(const int c, + const bool isTerminal, const bool inputIndexIncremented) { + addCharToCurrentWord(c); + mTerminalInputIndex = mInputIndex - (inputIndexIncremented ? 1 : 0); + mTerminalOutputIndex = mOutputIndex; + if (mNeedsToTraverseAllNodes && isTerminal) { + incrementOutputIndex(); + return TRAVERSE_ALL_ON_TERMINAL; + } else { + incrementOutputIndex(); + return TRAVERSE_ALL_NOT_ON_TERMINAL; + } +} + +inline Correction::CorrectionType Correction::processUnrelatedCorrectionType() { + // Needs to set mTerminalInputIndex and mTerminalOutputIndex before returning any CorrectionType + mTerminalInputIndex = mInputIndex; + mTerminalOutputIndex = mOutputIndex; + return UNRELATED; +} + +AK_FORCE_INLINE static void calcEditDistanceOneStep(int *editDistanceTable, const int *input, + const int inputSize, const int *output, const int outputLength) { + // TODO: Make sure that editDistance[0 ~ MAX_WORD_LENGTH_INTERNAL] is not touched. + // Let dp[i][j] be editDistanceTable[i * (inputSize + 1) + j]. + // Assuming that dp[0][0] ... dp[outputLength - 1][inputSize] are already calculated, + // and calculate dp[ouputLength][0] ... dp[outputLength][inputSize]. + int *const current = editDistanceTable + outputLength * (inputSize + 1); + const int *const prev = editDistanceTable + (outputLength - 1) * (inputSize + 1); + const int *const prevprev = + outputLength >= 2 ? editDistanceTable + (outputLength - 2) * (inputSize + 1) : 0; + current[0] = outputLength; + const int co = toBaseLowerCase(output[outputLength - 1]); + const int prevCO = outputLength >= 2 ? toBaseLowerCase(output[outputLength - 2]) : 0; + for (int i = 1; i <= inputSize; ++i) { + const int ci = toBaseLowerCase(input[i - 1]); + const uint16_t cost = (ci == co) ? 0 : 1; + current[i] = min(current[i - 1] + 1, min(prev[i] + 1, prev[i - 1] + cost)); + if (i >= 2 && prevprev && ci == prevCO && co == toBaseLowerCase(input[i - 2])) { + current[i] = min(current[i], prevprev[i - 2] + 1); + } + } +} + +AK_FORCE_INLINE void Correction::addCharToCurrentWord(const int c) { + mWord[mOutputIndex] = c; + const int *primaryInputWord = mProximityInfoState.getPrimaryInputWord(); + calcEditDistanceOneStep(mEditDistanceTable, primaryInputWord, mInputSize, mWord, + mOutputIndex + 1); +} + +inline int Correction::getFinalProbabilityInternal(const int probability, int **word, + int *wordLength, const int inputSize) { + const int outputIndex = mTerminalOutputIndex; + const int inputIndex = mTerminalInputIndex; + *wordLength = outputIndex + 1; + *word = mWord; + int finalProbability= Correction::RankingAlgorithm::calculateFinalProbability( + inputIndex, outputIndex, probability, mEditDistanceTable, this, inputSize); + return finalProbability; +} + } // namespace latinime #endif // LATINIME_CORRECTION_H diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h index e06ee42b0..bbef7329d 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -19,6 +19,12 @@ #include <stdint.h> +#ifdef __GNUC__ +#define AK_FORCE_INLINE __attribute__((always_inline)) __inline__ +#else // __GNUC__ +#define AK_FORCE_INLINE inline +#endif // __GNUC__ + #if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG) #include <android/log.h> #ifndef LOG_TAG @@ -30,17 +36,15 @@ #define DUMP_RESULT(words, frequencies, maxWordCount, maxWordLength) do { \ dumpResult(words, frequencies, maxWordCount, maxWordLength); } while (0) #define DUMP_WORD(word, length) do { dumpWord(word, length); } while (0) -#define DUMP_WORD_INT(word, length) do { dumpWordInt(word, length); } while (0) -// TODO: INTS_TO_CHARS -#define SHORTS_TO_CHARS(input, length, output) do { \ - shortArrayToCharArray(input, length, output); } while (0) +#define INTS_TO_CHARS(input, length, output) do { \ + intArrayToCharArray(input, length, output); } while (0) -static inline void dumpWordInfo(const unsigned short *word, const int length, - const int rank, const int frequency) { +static inline void dumpWordInfo(const int *word, const int length, const int rank, + const int frequency) { static char charBuf[50]; int i = 0; for (; i < length; ++i) { - const unsigned short c = word[i]; + const int c = word[i]; if (c == 0) { break; } @@ -53,8 +57,7 @@ static inline void dumpWordInfo(const unsigned short *word, const int length, } } -static inline void dumpResult( - const unsigned short *outWords, const int *frequencies, const int maxWordCounts, +static inline void dumpResult(const int *outWords, const int *frequencies, const int maxWordCounts, const int maxWordLength) { AKLOGI("--- DUMP RESULT ---------"); for (int i = 0; i < maxWordCounts; ++i) { @@ -63,11 +66,11 @@ static inline void dumpResult( AKLOGI("-------------------------"); } -static inline void dumpWord(const unsigned short *word, const int length) { +static AK_FORCE_INLINE void dumpWord(const int *word, const int length) { static char charBuf[50]; int i = 0; for (; i < length; ++i) { - const unsigned short c = word[i]; + const int c = word[i]; if (c == 0) { break; } @@ -80,22 +83,10 @@ static inline void dumpWord(const unsigned short *word, const int length) { } } -static inline void dumpWordInt(const int *word, const int length) { - static char charBuf[50]; - - for (int i = 0; i < length; ++i) { - charBuf[i] = word[i]; - } - charBuf[length] = 0; - AKLOGI("i[ %s ]", charBuf); -} - -// TODO: Change this to intArrayToCharArray -static inline void shortArrayToCharArray( - const unsigned short *input, const int length, char *output) { +static inline void intArrayToCharArray(const int *input, const int length, char *output) { int i = 0; - for (;i < length; ++i) { - const unsigned short c = input[i]; + for (; i < length; ++i) { + const int c = input[i]; if (c == 0) { break; } @@ -137,11 +128,9 @@ static inline void showStackTrace() { #define AKLOGI(fmt, ...) #define DUMP_RESULT(words, frequencies, maxWordCount, maxWordLength) #define DUMP_WORD(word, length) -#define DUMP_WORD_INT(word, length) #define ASSERT(success) #define SHOW_STACK_TRACE -// TODO: INTS_TO_CHARS -#define SHORTS_TO_CHARS(input, length, output) +#define INTS_TO_CHARS(input, length, output) #endif #ifdef FLAG_DO_PROFILE @@ -219,6 +208,8 @@ static inline void prof_out(void) { #define DEBUG_CORRECTION false #define DEBUG_CORRECTION_FREQ false #define DEBUG_WORDS_PRIORITY_QUEUE false +#define DEBUG_SAMPLING_POINTS true +#define DEBUG_POINTS_PROBABILITY true #ifdef FLAG_FULL_DBG #define DEBUG_GEO_FULL true @@ -239,6 +230,8 @@ static inline void prof_out(void) { #define DEBUG_CORRECTION false #define DEBUG_CORRECTION_FREQ false #define DEBUG_WORDS_PRIORITY_QUEUE false +#define DEBUG_SAMPLING_POINTS false +#define DEBUG_POINTS_PROBABILITY false #define DEBUG_GEO_FULL false @@ -288,6 +281,8 @@ static inline void prof_out(void) { #define NOT_A_PROBABILITY (-1) #define KEYCODE_SPACE ' ' +#define KEYCODE_SINGLE_QUOTE '\'' +#define KEYCODE_HYPHEN_MINUS '-' #define CALIBRATE_SCORE_BY_TOUCH_COORDINATES true @@ -350,8 +345,8 @@ static inline void prof_out(void) { #define MULTIPLE_WORDS_DEMOTION_RATE 80 #define MIN_INPUT_LENGTH_FOR_THREE_OR_MORE_WORDS_CORRECTION 6 -#define TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.35 -#define START_TWO_WORDS_CORRECTION_THRESHOLD 0.185 +#define TWO_WORDS_CORRECTION_WITH_OTHER_ERROR_THRESHOLD 0.35f +#define START_TWO_WORDS_CORRECTION_THRESHOLD 0.185f /* heuristic... This should be changed if we change the unit of the frequency. */ #define SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ (MAX_FREQ * 58 / 100) @@ -398,6 +393,8 @@ static inline void prof_out(void) { template<typename T> inline T min(T a, T b) { return a < b ? a : b; } template<typename T> inline T max(T a, T b) { return a > b ? a : b; } +#define NELEMS(x) (sizeof(x) / sizeof((x)[0])) + // The ratio of neutral area radius to sweet spot radius. #define NEUTRAL_AREA_RADIUS_RATIO 1.3f diff --git a/native/jni/src/dic_traverse_wrapper.h b/native/jni/src/dic_traverse_wrapper.h index 292382487..3fe3d5b74 100644 --- a/native/jni/src/dic_traverse_wrapper.h +++ b/native/jni/src/dic_traverse_wrapper.h @@ -62,6 +62,5 @@ class DicTraverseWrapper { void *, const Dictionary *const, const int *, const int); static void (*sDicTraverseSessionReleaseMethod)(void *); }; -int register_DicTraverseSession(JNIEnv *env); } // namespace latinime #endif // LATINIME_DIC_TRAVERSE_WRAPPER_H diff --git a/native/jni/src/dictionary.cpp b/native/jni/src/dictionary.cpp index 2fbe83e86..963ba4645 100644 --- a/native/jni/src/dictionary.cpp +++ b/native/jni/src/dictionary.cpp @@ -29,14 +29,13 @@ 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 maxPredictions) +Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int maxWordLength, + int maxWords, int maxPredictions) : mDict(static_cast<unsigned char *>(dict)), mOffsetDict((static_cast<unsigned char *>(dict)) + BinaryFormat::getHeaderSize(mDict)), mDictSize(dictSize), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust), - mUnigramDictionary(new UnigramDictionary(mOffsetDict, typedLetterMultiplier, - fullWordMultiplier, maxWordLength, maxWords, BinaryFormat::getFlags(mDict))), + mUnigramDictionary(new UnigramDictionary(mOffsetDict, maxWordLength, maxWords, + BinaryFormat::getFlags(mDict))), mBigramDictionary(new BigramDictionary(mOffsetDict, maxWordLength, maxPredictions)), mGestureDecoder(new GestureDecoderWrapper(maxWordLength, maxWords)) { if (DEBUG_DICT) { @@ -55,11 +54,10 @@ Dictionary::~Dictionary() { } int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, - int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, - int *codes, int codesSize, int *prevWordChars, - int prevWordLength, int commitPoint, bool isGesture, - bool useFullEditDistance, unsigned short *outWords, - int *frequencies, int *spaceIndices, int *outputTypes) const { + int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *codes, + int codesSize, int *prevWordChars, int prevWordLength, int commitPoint, bool isGesture, + bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices, + int *outputTypes) const { int result = 0; if (isGesture) { DicTraverseWrapper::initDicTraverseSession( @@ -84,7 +82,7 @@ int Dictionary::getSuggestions(ProximityInfo *proximityInfo, void *traverseSessi } int Dictionary::getBigrams(const int32_t *word, int length, int *codes, int codesSize, - unsigned short *outWords, int *frequencies, int *outputTypes) const { + int *outWords, int *frequencies, int *outputTypes) const { if (length <= 0) return 0; return mBigramDictionary->getBigrams(word, length, codes, codesSize, outWords, frequencies, outputTypes); diff --git a/native/jni/src/dictionary.h b/native/jni/src/dictionary.h index a1358890d..dd97e9a88 100644 --- a/native/jni/src/dictionary.h +++ b/native/jni/src/dictionary.h @@ -41,17 +41,17 @@ class Dictionary { const static int KIND_SHORTCUT = 7; // A shortcut const static int KIND_PREDICTION = 8; // A prediction (== a suggestion with no input) - Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int typedLetterMultipler, - int fullWordMultiplier, int maxWordLength, int maxWords, int maxPredictions); + Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust, int maxWordLength, + int maxWords, int maxPredictions); int getSuggestions(ProximityInfo *proximityInfo, void *traverseSession, int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *codes, int codesSize, int *prevWordChars, int prevWordLength, int commitPoint, bool isGesture, - bool useFullEditDistance, unsigned short *outWords, - int *frequencies, int *spaceIndices, int *outputTypes) const; + bool useFullEditDistance, int *outWords, int *frequencies, int *spaceIndices, + int *outputTypes) const; - int getBigrams(const int32_t *word, int length, int *codes, int codesSize, - unsigned short *outWords, int *frequencies, int *outputTypes) const; + int getBigrams(const int32_t *word, int length, int *codes, int codesSize, int *outWords, + int *frequencies, int *outputTypes) const; int getFrequency(const int32_t *word, int length) const; bool isValidBigram(const int32_t *word1, int length1, const int32_t *word2, int length2) const; @@ -68,7 +68,7 @@ class Dictionary { // public static utility methods // static inline methods should be defined in the header file - static int wideStrLen(unsigned short *str); + static int wideStrLen(int *str); private: DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary); @@ -88,7 +88,7 @@ class Dictionary { // public static utility methods // static inline methods should be defined in the header file -inline int Dictionary::wideStrLen(unsigned short *str) { +inline int Dictionary::wideStrLen(int *str) { if (!str) return 0; int length = 0; while (*str) { diff --git a/native/jni/src/geometry_utils.h b/native/jni/src/geometry_utils.h index 31359e19d..38b91cc50 100644 --- a/native/jni/src/geometry_utils.h +++ b/native/jni/src/geometry_utils.h @@ -19,38 +19,46 @@ #include <cmath> +#include "defines.h" + #define DEBUG_DECODER false #define M_PI_F 3.14159265f #define ROUND_FLOAT_10000(f) ((f) < 1000.0f && (f) > 0.001f) \ ? (floorf((f) * 10000.0f) / 10000.0f) : (f) -#define SQUARE_FLOAT(x) ((x) * (x)) namespace latinime { -static inline float getSquaredDistanceFloat(float x1, float y1, float x2, float y2) { - const float deltaX = x1 - x2; - const float deltaY = y1 - y2; - return SQUARE_FLOAT(deltaX) + SQUARE_FLOAT(deltaY); +static inline float SQUARE_FLOAT(const float x) { return x * x; } + +static inline float getSquaredDistanceFloat(const float x1, const float y1, const float x2, + const float y2) { + return SQUARE_FLOAT(x1 - x2) + SQUARE_FLOAT(y1 - y2); +} + +static inline float getNormalizedSquaredDistanceFloat(const float x1, const float y1, + const float x2, const float y2, const float scale) { + return getSquaredDistanceFloat(x1, y1, x2, y2) / SQUARE_FLOAT(scale); } -static inline float getDistanceFloat(float x1, float y1, float x2, float y2) { +static inline float getDistanceFloat(const float x1, const float y1, const float x2, + const float y2) { return hypotf(x1 - x2, y1 - y2); } -static inline int getDistanceInt(int x1, int y1, int x2, int y2) { +static AK_FORCE_INLINE int getDistanceInt(const int x1, const int y1, const int x2, const int y2) { return static_cast<int>(getDistanceFloat(static_cast<float>(x1), static_cast<float>(y1), static_cast<float>(x2), static_cast<float>(y2))); } -static inline float getAngle(int x1, int y1, int x2, int y2) { +static AK_FORCE_INLINE float getAngle(const int x1, const int y1, const int x2, const int y2) { const int dx = x1 - x2; const int dy = y1 - y2; if (dx == 0 && dy == 0) return 0; return atan2f(static_cast<float>(dy), static_cast<float>(dx)); } -static inline float getAngleDiff(float a1, float a2) { +static AK_FORCE_INLINE float getAngleDiff(const float a1, const float a2) { const float deltaA = fabsf(a1 - a2); const float diff = ROUND_FLOAT_10000(deltaA); if (diff > M_PI_F) { @@ -60,8 +68,8 @@ static inline float getAngleDiff(float a1, float a2) { return diff; } -static inline float pointToLineSegSquaredDistanceFloat( - float x, float y, float x1, float y1, float x2, float y2, bool extend) { +static inline float pointToLineSegSquaredDistanceFloat(const float x, const float y, const float x1, + const float y1, const float x2, const float y2, const bool extend) { const float ray1x = x - x1; const float ray1y = y - y1; const float ray2x = x2 - x1; @@ -85,5 +93,25 @@ static inline float pointToLineSegSquaredDistanceFloat( } return getSquaredDistanceFloat(x, y, projectionX, projectionY); } + +// Normal distribution N(u, sigma^2). +struct NormalDistribution { + NormalDistribution(const float u, const float sigma) + : mU(u), mSigma(sigma), + mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * SQUARE_FLOAT(sigma))), + mPreComputedExponentPart(-1.0f / (2.0f * SQUARE_FLOAT(sigma))) {} + + float getProbabilityDensity(const float x) const { + const float shiftedX = x - mU; + return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX)); + } + +private: + DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution); + float mU; // mean value + float mSigma; // standard deviation + float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2) + float mPreComputedExponentPart; // = -1 / (2 * sigma^2) +}; // struct NormalDistribution } // namespace latinime #endif // LATINIME_GEOMETRY_UTILS_H diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.cpp b/native/jni/src/gesture/gesture_decoder_wrapper.cpp index afbe0c5c3..20ad4a58c 100644 --- a/native/jni/src/gesture/gesture_decoder_wrapper.cpp +++ b/native/jni/src/gesture/gesture_decoder_wrapper.cpp @@ -19,4 +19,8 @@ namespace latinime { IncrementalDecoderInterface * (*GestureDecoderWrapper::sGestureDecoderFactoryMethod)(int, int) = 0; + + GestureDecoderWrapper::~GestureDecoderWrapper() { + delete mIncrementalDecoderInterface; + } } // namespace latinime diff --git a/native/jni/src/gesture/gesture_decoder_wrapper.h b/native/jni/src/gesture/gesture_decoder_wrapper.h index 92e1ded49..5b056b647 100644 --- a/native/jni/src/gesture/gesture_decoder_wrapper.h +++ b/native/jni/src/gesture/gesture_decoder_wrapper.h @@ -33,20 +33,17 @@ class GestureDecoderWrapper : public IncrementalDecoderInterface { : mIncrementalDecoderInterface(getGestureDecoderInstance(maxWordLength, maxWords)) { } - virtual ~GestureDecoderWrapper() { - delete mIncrementalDecoderInterface; - } + virtual ~GestureDecoderWrapper(); int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs, - int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, - unsigned short *outWords, int *frequencies, int *outputIndices, - int *outputTypes) const { + int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords, + int *frequencies, int *outputIndices, int *outputTypes) const { if (!mIncrementalDecoderInterface) { return 0; } - return mIncrementalDecoderInterface->getSuggestions( - pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes, - inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes); + return mIncrementalDecoderInterface->getSuggestions(pInfo, traverseSession, inputXs, + inputYs, times, pointerIds, codes, inputSize, commitPoint, outWords, frequencies, + outputIndices, outputTypes); } static void setGestureDecoderFactoryMethod( diff --git a/native/jni/src/gesture/incremental_decoder_interface.h b/native/jni/src/gesture/incremental_decoder_interface.h index d1395aab9..e41513dbc 100644 --- a/native/jni/src/gesture/incremental_decoder_interface.h +++ b/native/jni/src/gesture/incremental_decoder_interface.h @@ -28,10 +28,9 @@ class ProximityInfo; class IncrementalDecoderInterface { public: - virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession, - int *inputXs, int *inputYs, int *times, int *pointerIds, int *codes, - int inputSize, int commitPoint, unsigned short *outWords, int *frequencies, - int *outputIndices, int *outputTypes) const = 0; + virtual int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, + int *inputYs, int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, + int *outWords, int *frequencies, int *outputIndices, int *outputTypes) const = 0; IncrementalDecoderInterface() { }; virtual ~IncrementalDecoderInterface() { }; private: diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.cpp b/native/jni/src/gesture/incremental_decoder_wrapper.cpp index 8fcda6c9e..f6e45623a 100644 --- a/native/jni/src/gesture/incremental_decoder_wrapper.cpp +++ b/native/jni/src/gesture/incremental_decoder_wrapper.cpp @@ -19,4 +19,8 @@ namespace latinime { IncrementalDecoderInterface * (*IncrementalDecoderWrapper::sIncrementalDecoderFactoryMethod)(int, int) = 0; + + IncrementalDecoderWrapper::~IncrementalDecoderWrapper() { + delete mIncrementalDecoderInterface; + } } // namespace latinime diff --git a/native/jni/src/gesture/incremental_decoder_wrapper.h b/native/jni/src/gesture/incremental_decoder_wrapper.h index da7afdb8a..7d16560ef 100644 --- a/native/jni/src/gesture/incremental_decoder_wrapper.h +++ b/native/jni/src/gesture/incremental_decoder_wrapper.h @@ -33,20 +33,17 @@ class IncrementalDecoderWrapper : public IncrementalDecoderInterface { : mIncrementalDecoderInterface(getIncrementalDecoderInstance(maxWordLength, maxWords)) { } - virtual ~IncrementalDecoderWrapper() { - delete mIncrementalDecoderInterface; - } + virtual ~IncrementalDecoderWrapper(); int getSuggestions(ProximityInfo *pInfo, void *traverseSession, int *inputXs, int *inputYs, - int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, - unsigned short *outWords, int *frequencies, int *outputIndices, - int *outputTypes) const { + int *times, int *pointerIds, int *codes, int inputSize, int commitPoint, int *outWords, + int *frequencies, int *outputIndices, int *outputTypes) const { if (!mIncrementalDecoderInterface) { return 0; } - return mIncrementalDecoderInterface->getSuggestions( - pInfo, traverseSession, inputXs, inputYs, times, pointerIds, codes, - inputSize, commitPoint, outWords, frequencies, outputIndices, outputTypes); + return mIncrementalDecoderInterface->getSuggestions(pInfo, traverseSession, inputXs, + inputYs, times, pointerIds, codes, inputSize, commitPoint, outWords, frequencies, + outputIndices, outputTypes); } static void setIncrementalDecoderFactoryMethod( diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp index fde93b5a9..84db7c196 100644 --- a/native/jni/src/proximity_info.cpp +++ b/native/jni/src/proximity_info.cpp @@ -31,21 +31,21 @@ namespace latinime { /* static */ const float ProximityInfo::NOT_A_DISTANCE_FLOAT = -1.0f; -static inline void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, jsize len, - jint *buffer) { +static AK_FORCE_INLINE void safeGetOrFillZeroIntArrayRegion(JNIEnv *env, jintArray jArray, + jsize len, jint *buffer) { if (jArray && buffer) { env->GetIntArrayRegion(jArray, 0, len, buffer); } else if (buffer) { - memset(buffer, 0, len * sizeof(jint)); + memset(buffer, 0, len * sizeof(buffer[0])); } } -static inline void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray, jsize len, - jfloat *buffer) { +static AK_FORCE_INLINE void safeGetOrFillZeroFloatArrayRegion(JNIEnv *env, jfloatArray jArray, + jsize len, jfloat *buffer) { if (jArray && buffer) { env->GetFloatArrayRegion(jArray, 0, len, buffer); } else if (buffer) { - memset(buffer, 0, len * sizeof(jfloat)); + memset(buffer, 0, len * sizeof(buffer[0])); } } @@ -127,13 +127,6 @@ bool ProximityInfo::hasSpaceProximity(const int x, const int y) const { return false; } -static inline float getNormalizedSquaredDistanceFloat(float x1, float y1, float x2, float y2, - float scale) { - const float deltaX = x1 - x2; - const float deltaY = y1 - y2; - return (SQUARE_FLOAT(deltaX) + SQUARE_FLOAT(deltaY)) / SQUARE_FLOAT(scale); -} - float ProximityInfo::getNormalizedSquaredDistanceFromCenterFloatG( const int keyId, const int x, const int y) const { const static float verticalSweetSpotScaleForGeometric = 1.1f; @@ -239,6 +232,9 @@ int ProximityInfo::getKeyIndexOf(const int c) const { // We do not have the coordinate data return NOT_AN_INDEX; } + if (c == NOT_A_CODE_POINT) { + return NOT_AN_INDEX; + } const int lowerCode = static_cast<int>(toLowerCase(c)); hash_map_compat<int, int>::const_iterator mapPos = mCodeToKeyMap.find(lowerCode); if (mapPos != mCodeToKeyMap.end()) { @@ -296,9 +292,7 @@ int ProximityInfo::getKeyCenterYOfKeyIdG(int keyId) const { return 0; } -int ProximityInfo::getKeyKeyDistanceG(int key0, int key1) const { - const int keyId0 = getKeyIndexOf(key0); - const int keyId1 = getKeyIndexOf(key1); +int ProximityInfo::getKeyKeyDistanceG(const int keyId0, const int keyId1) const { if (keyId0 >= 0 && keyId1 >= 0) { return mKeyKeyDistancesG[keyId0][keyId1]; } diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h index 70942aa19..7ee15d578 100644 --- a/native/jni/src/proximity_info.h +++ b/native/jni/src/proximity_info.h @@ -109,7 +109,7 @@ class ProximityInfo { int getKeyCenterYOfCodePointG(int charCode) const; int getKeyCenterXOfKeyIdG(int keyId) const; int getKeyCenterYOfKeyIdG(int keyId) const; - int getKeyKeyDistanceG(int key0, int key1) const; + int getKeyKeyDistanceG(int keyId0, int keyId1) const; private: DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo); diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp index 392ec8194..e64d46d01 100644 --- a/native/jni/src/proximity_info_state.cpp +++ b/native/jni/src/proximity_info_state.cpp @@ -15,6 +15,7 @@ */ #include <cstring> // for memset() +#include <sstream> // for debug prints #include <stdint.h> #define LOG_TAG "LatinIME: proximity_info_state.cpp" @@ -33,7 +34,7 @@ const float ProximityInfoState::NOT_A_DISTANCE_FLOAT = -1.0f; const int ProximityInfoState::NOT_A_CODE = -1; void ProximityInfoState::initInputParams(const int pointerId, const float maxPointToKeyLength, - const ProximityInfo *proximityInfo, const int32_t *const inputCodes, const int inputSize, + const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times, const int *const pointerIds, const bool isGeometric) { @@ -62,7 +63,7 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi // - mNormalizedSquaredDistances // TODO: Merge for (int i = 0; i < inputSize; ++i) { - const int32_t primaryKey = inputCodes[i]; + const int primaryKey = inputCodes[i]; const int x = xCoordinates[i]; const int y = yCoordinates[i]; int *proximities = &mInputCodes[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL]; @@ -94,23 +95,26 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi pushTouchPointStartIndex = mInputIndice[mInputIndice.size() - 2]; popInputData(); popInputData(); - lastSavedInputSize = mInputXs.size(); + lastSavedInputSize = mSampledInputXs.size(); } else { // Clear all data. - mInputXs.clear(); - mInputYs.clear(); + mSampledInputXs.clear(); + mSampledInputYs.clear(); mTimes.clear(); mInputIndice.clear(); mLengthCache.clear(); mDistanceCache.clear(); mNearKeysVector.clear(); + mSearchKeysVector.clear(); mRelativeSpeeds.clear(); + mCharProbabilities.clear(); + mDirections.clear(); } if (DEBUG_GEO_FULL) { AKLOGI("Init ProximityInfoState: reused points = %d, last input size = %d", pushTouchPointStartIndex, lastSavedInputSize); } - mInputSize = 0; + mSampledInputSize = 0; if (xCoordinates && yCoordinates) { const bool proximityOnly = !isGeometric && (xCoordinates[0] < 0 || yCoordinates[0] < 0); @@ -130,6 +134,10 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi NearKeysDistanceMap *currentNearKeysDistances = &nearKeysDistances[0]; NearKeysDistanceMap *prevNearKeysDistances = &nearKeysDistances[1]; NearKeysDistanceMap *prevPrevNearKeysDistances = &nearKeysDistances[2]; + // "sumAngle" is accumulated by each angle of input points. And when "sumAngle" exceeds + // the threshold we save that point, reset sumAngle. This aims to keep the figure of + // the curve. + float sumAngle = 0.0f; for (int i = pushTouchPointStartIndex; i <= lastInputIndex; ++i) { // Assuming pointerId == 0 if pointerIds is null. @@ -138,13 +146,22 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi AKLOGI("Init ProximityInfoState: (%d)PID = %d", i, pid); } if (pointerId == pid) { - const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCharAt(i); + const int c = isGeometric ? NOT_A_COORDINATE : getPrimaryCodePointAt(i); const int x = proximityOnly ? NOT_A_COORDINATE : xCoordinates[i]; const int y = proximityOnly ? NOT_A_COORDINATE : yCoordinates[i]; const int time = times ? times[i] : -1; + + if (i > 1) { + const float prevAngle = getAngle(xCoordinates[i - 2], yCoordinates[i - 2], + xCoordinates[i - 1], yCoordinates[i - 1]); + const float currentAngle = + getAngle(xCoordinates[i - 1], yCoordinates[i - 1], x, y); + sumAngle += getAngleDiff(prevAngle, currentAngle); + } + if (pushTouchPoint(i, c, x, y, time, isGeometric /* do sampling */, - i == lastInputIndex, currentNearKeysDistances, prevNearKeysDistances, - prevPrevNearKeysDistances)) { + i == lastInputIndex, sumAngle, currentNearKeysDistances, + prevNearKeysDistances, prevPrevNearKeysDistances)) { // Previous point information was popped. NearKeysDistanceMap *tmp = prevNearKeysDistances; prevNearKeysDistances = currentNearKeysDistances; @@ -154,97 +171,104 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi prevPrevNearKeysDistances = prevNearKeysDistances; prevNearKeysDistances = currentNearKeysDistances; currentNearKeysDistances = tmp; + sumAngle = 0.0f; } } } - mInputSize = mInputXs.size(); - } - - if (mInputSize > 0 && isGeometric) { - int sumDuration = mTimes.back() - mTimes.front(); - int sumLength = mLengthCache.back() - mLengthCache.front(); - float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration); - mRelativeSpeeds.resize(mInputSize); - for (int i = lastSavedInputSize; i < mInputSize; ++i) { - const int index = mInputIndice[i]; - int length = 0; - int duration = 0; - if (index == 0 && index < inputSize - 1) { - length = getDistanceInt(xCoordinates[index], yCoordinates[index], - xCoordinates[index + 1], yCoordinates[index + 1]); - duration = times[index + 1] - times[index]; - } else if (index == inputSize - 1 && index > 0) { - length = getDistanceInt(xCoordinates[index - 1], yCoordinates[index - 1], - xCoordinates[index], yCoordinates[index]); - duration = times[index] - times[index - 1]; - } else if (0 < index && index < inputSize - 1) { - length = getDistanceInt(xCoordinates[index - 1], yCoordinates[index - 1], - xCoordinates[index], yCoordinates[index]) - + getDistanceInt(xCoordinates[index], yCoordinates[index], - xCoordinates[index + 1], yCoordinates[index + 1]); - duration = times[index + 1] - times[index - 1]; - } else { - length = 0; - duration = 1; - } - const float speed = static_cast<float>(length) / static_cast<float>(duration); - mRelativeSpeeds[i] = speed / averageSpeed; + mSampledInputSize = mSampledInputXs.size(); + } + + if (mSampledInputSize > 0 && isGeometric) { + refreshRelativeSpeed(inputSize, xCoordinates, yCoordinates, times, lastSavedInputSize); + } + + if (DEBUG_GEO_FULL) { + for (int i = 0; i < mSampledInputSize; ++i) { + AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, mSampledInputXs[i], + mSampledInputYs[i], mTimes[i]); } } - if (mInputSize > 0) { + if (mSampledInputSize > 0) { const int keyCount = mProximityInfo->getKeyCount(); - mNearKeysVector.resize(mInputSize); - mDistanceCache.resize(mInputSize * keyCount); - for (int i = lastSavedInputSize; i < mInputSize; ++i) { + mNearKeysVector.resize(mSampledInputSize); + mSearchKeysVector.resize(mSampledInputSize); + mDistanceCache.resize(mSampledInputSize * keyCount); + for (int i = lastSavedInputSize; i < mSampledInputSize; ++i) { mNearKeysVector[i].reset(); + mSearchKeysVector[i].reset(); static const float NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD = 4.0f; for (int k = 0; k < keyCount; ++k) { const int index = i * keyCount + k; - const int x = mInputXs[i]; - const int y = mInputYs[i]; + const int x = mSampledInputXs[i]; + const int y = mSampledInputYs[i]; const float normalizedSquaredDistance = mProximityInfo->getNormalizedSquaredDistanceFromCenterFloatG(k, x, y); mDistanceCache[index] = normalizedSquaredDistance; if (normalizedSquaredDistance < NEAR_KEY_NORMALIZED_SQUARED_THRESHOLD) { - mNearKeysVector[i].set(k, 1); + mNearKeysVector[i][k] = true; } } } + if (isGeometric) { + // updates probabilities of skipping or mapping each key for all points. + updateAlignPointProbabilities(lastSavedInputSize); - static const float READ_FORWORD_LENGTH_SCALE = 0.95f; - const int readForwordLength = static_cast<int>( - hypotf(mProximityInfo->getKeyboardWidth(), mProximityInfo->getKeyboardHeight()) - * READ_FORWORD_LENGTH_SCALE); - for (int i = 0; i < mInputSize; ++i) { - if (DEBUG_GEO_FULL) { - AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, mInputXs[i], mInputYs[i], - mTimes[i]); - } - for (int j = max(i + 1, lastSavedInputSize); j < mInputSize; ++j) { - if (mLengthCache[j] - mLengthCache[i] >= readForwordLength) { - break; + static const float READ_FORWORD_LENGTH_SCALE = 0.95f; + const int readForwordLength = static_cast<int>( + hypotf(mProximityInfo->getKeyboardWidth(), mProximityInfo->getKeyboardHeight()) + * READ_FORWORD_LENGTH_SCALE); + for (int i = 0; i < mSampledInputSize; ++i) { + if (i >= lastSavedInputSize) { + mSearchKeysVector[i].reset(); + } + for (int j = max(i, lastSavedInputSize); j < mSampledInputSize; ++j) { + if (mLengthCache[j] - mLengthCache[i] >= readForwordLength) { + break; + } + mSearchKeysVector[i] |= mNearKeysVector[j]; } - mNearKeysVector[i] |= mNearKeysVector[j]; } } } + if (DEBUG_SAMPLING_POINTS) { + std::stringstream originalX, originalY, sampledX, sampledY; + for (int i = 0; i < inputSize; ++i) { + originalX << xCoordinates[i]; + originalY << yCoordinates[i]; + if (i != inputSize - 1) { + originalX << ";"; + originalY << ";"; + } + } + for (int i = 0; i < mSampledInputSize; ++i) { + sampledX << mSampledInputXs[i]; + sampledY << mSampledInputYs[i]; + if (i != mSampledInputSize - 1) { + sampledX << ";"; + sampledY << ";"; + } + } + AKLOGI("original points:\n%s, %s,\nsampled points:\n%s, %s,\n", + originalX.str().c_str(), originalY.str().c_str(), sampledX.str().c_str(), + sampledY.str().c_str()); + } // end /////////////////////// memset(mNormalizedSquaredDistances, NOT_A_DISTANCE, sizeof(mNormalizedSquaredDistances)); memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); - mTouchPositionCorrectionEnabled = mInputSize > 0 && mHasTouchPositionCorrectionData + mTouchPositionCorrectionEnabled = mSampledInputSize > 0 && mHasTouchPositionCorrectionData && xCoordinates && yCoordinates; if (!isGeometric && pointerId == 0) { for (int i = 0; i < inputSize; ++i) { - mPrimaryInputWord[i] = getPrimaryCharAt(i); + mPrimaryInputWord[i] = getPrimaryCodePointAt(i); } - for (int i = 0; i < mInputSize && mTouchPositionCorrectionEnabled; ++i) { - const int *proximityChars = getProximityCharsAt(i); - const int primaryKey = proximityChars[0]; + for (int i = 0; i < mSampledInputSize && mTouchPositionCorrectionEnabled; ++i) { + const int *proximityCodePoints = getProximityCodePointsAt(i); + const int primaryKey = proximityCodePoints[0]; const int x = xCoordinates[i]; const int y = yCoordinates[i]; if (DEBUG_PROXIMITY_CHARS) { @@ -252,11 +276,12 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi a += 0; AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y); } - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityChars[j] > 0; ++j) { - const int currentChar = proximityChars[j]; + for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL && proximityCodePoints[j] > 0; + ++j) { + const int currentCodePoint = proximityCodePoints[j]; const float squaredDistance = hasInputCoordinates() ? calculateNormalizedSquaredDistance( - mProximityInfo->getKeyIndexOf(currentChar), i) : + mProximityInfo->getKeyIndexOf(currentCodePoint), i) : NOT_A_DISTANCE_FLOAT; if (squaredDistance >= 0.0f) { mNormalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + j] = @@ -267,23 +292,71 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO; } if (DEBUG_PROXIMITY_CHARS) { - AKLOGI("--- Proximity (%d) = %c", j, currentChar); + AKLOGI("--- Proximity (%d) = %c", j, currentCodePoint); } } } } if (DEBUG_GEO_FULL) { - AKLOGI("ProximityState init finished: %d points out of %d", mInputSize, inputSize); + AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize); + } +} + +void ProximityInfoState::refreshRelativeSpeed(const int inputSize, const int *const xCoordinates, + const int *const yCoordinates, const int *const times, const int lastSavedInputSize) { + // Relative speed calculation. + const int sumDuration = mTimes.back() - mTimes.front(); + const int sumLength = mLengthCache.back() - mLengthCache.front(); + const float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration); + mRelativeSpeeds.resize(mSampledInputSize); + for (int i = lastSavedInputSize; i < mSampledInputSize; ++i) { + const int index = mInputIndice[i]; + int length = 0; + int duration = 0; + + // Calculate velocity by using distances and durations of + // NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and backward. + static const int NUM_POINTS_FOR_SPEED_CALCULATION = 2; + for (int j = index; j < min(inputSize - 1, index + NUM_POINTS_FOR_SPEED_CALCULATION); + ++j) { + if (i < mSampledInputSize - 1 && j >= mInputIndice[i + 1]) { + break; + } + length += getDistanceInt(xCoordinates[j], yCoordinates[j], + xCoordinates[j + 1], yCoordinates[j + 1]); + duration += times[j + 1] - times[j]; + } + for (int j = index - 1; j >= max(0, index - NUM_POINTS_FOR_SPEED_CALCULATION); --j) { + if (i > 0 && j < mInputIndice[i - 1]) { + break; + } + length += getDistanceInt(xCoordinates[j], yCoordinates[j], + xCoordinates[j + 1], yCoordinates[j + 1]); + duration += times[j + 1] - times[j]; + } + if (duration == 0 || sumDuration == 0) { + // Cannot calculate speed; thus, it gives an average value (1.0); + mRelativeSpeeds[i] = 1.0f; + } else { + const float speed = static_cast<float>(length) / static_cast<float>(duration); + mRelativeSpeeds[i] = speed / averageSpeed; + } + } + + // Direction calculation. + mDirections.resize(mSampledInputSize - 1); + for (int i = max(0, lastSavedInputSize - 1); i < mSampledInputSize - 1; ++i) { + mDirections[i] = getDirection(i, i + 1); } } bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times) { - for (int i = 0; i < mInputSize; ++i) { + for (int i = 0; i < mSampledInputSize; ++i) { const int index = mInputIndice[i]; - if (index > inputSize || xCoordinates[index] != mInputXs[i] || - yCoordinates[index] != mInputYs[i] || times[index] != mTimes[i]) { + if (index > inputSize || xCoordinates[index] != mSampledInputXs[i] || + yCoordinates[index] != mSampledInputYs[i] || times[index] != mTimes[i]) { return false; } } @@ -294,7 +367,7 @@ bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSiz // the given point and the nearest key position. float ProximityInfoState::updateNearKeysDistances(const int x, const int y, NearKeysDistanceMap *const currentNearKeysDistances) { - static const float NEAR_KEY_THRESHOLD = 4.0f; + static const float NEAR_KEY_THRESHOLD = 2.0f; currentNearKeysDistances->clear(); const int keyCount = mProximityInfo->getKeyCount(); @@ -332,64 +405,49 @@ bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const current // Calculating a point score that indicates usefulness of the point. float ProximityInfoState::getPointScore( const int x, const int y, const int time, const bool lastPoint, const float nearest, - const NearKeysDistanceMap *const currentNearKeysDistances, + const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances, const NearKeysDistanceMap *const prevNearKeysDistances, const NearKeysDistanceMap *const prevPrevNearKeysDistances) const { static const int DISTANCE_BASE_SCALE = 100; - static const int SAVE_DISTANCE_SCALE = 200; - static const int SKIP_DISTANCE_SCALE = 25; - static const int CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 40; - static const int STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 50; - static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 27; - static const float SAVE_DISTANCE_SCORE = 2.0f; - static const float SKIP_DISTANCE_SCORE = -1.0f; - static const float CHECK_LOCALMIN_DISTANCE_SCORE = -1.0f; - static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 36.0f; - static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f; - static const float STRAIGHT_SKIP_SCORE = -1.0f; - static const float CORNER_ANGLE_THRESHOLD = M_PI_F / 2.0f; + static const float NEAR_KEY_THRESHOLD = 0.6f; + static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 25; + static const float NOT_LOCALMIN_DISTANCE_SCORE = -1.0f; + static const float LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE = 1.0f; + static const float CORNER_ANGLE_THRESHOLD = M_PI_F * 2.0f / 3.0f; + static const float CORNER_SUM_ANGLE_THRESHOLD = M_PI_F / 4.0f; static const float CORNER_SCORE = 1.0f; - const std::size_t size = mInputXs.size(); - if (size <= 1) { + const size_t size = mSampledInputXs.size(); + // If there is only one point, add this point. Besides, if the previous point's distance map + // is empty, we re-compute nearby keys distances from the current point. + // Note that the current point is the first point in the incremental input that needs to + // be re-computed. + if (size <= 1 || prevNearKeysDistances->empty()) { return 0.0f; } + const int baseSampleRate = mProximityInfo->getMostCommonKeyWidth(); - const int distNext = getDistanceInt(x, y, mInputXs.back(), mInputYs.back()) - * DISTANCE_BASE_SCALE; - const int distPrev = getDistanceInt(mInputXs.back(), mInputYs.back(), - mInputXs[size - 2], mInputYs[size - 2]) * DISTANCE_BASE_SCALE; + const int distPrev = getDistanceInt(mSampledInputXs.back(), mSampledInputYs.back(), + mSampledInputXs[size - 2], mSampledInputYs[size - 2]) * DISTANCE_BASE_SCALE; float score = 0.0f; - // Sum of distances - if (distPrev + distNext > baseSampleRate * SAVE_DISTANCE_SCALE) { - score += SAVE_DISTANCE_SCORE; - } - // Distance - if (distPrev < baseSampleRate * SKIP_DISTANCE_SCALE) { - score += SKIP_DISTANCE_SCORE; - } // Location - if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) { - if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances, - prevPrevNearKeysDistances)) { - score += CHECK_LOCALMIN_DISTANCE_SCORE; - } + if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances, + prevPrevNearKeysDistances)) { + score += NOT_LOCALMIN_DISTANCE_SCORE; + } else if (nearest < NEAR_KEY_THRESHOLD) { + // Promote points nearby keys + score += LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE; } // Angle - const float angle1 = getAngle(x, y, mInputXs.back(), mInputYs.back()); - const float angle2 = getAngle(mInputXs.back(), mInputYs.back(), - mInputXs[size - 2], mInputYs[size - 2]); + const float angle1 = getAngle(x, y, mSampledInputXs.back(), mSampledInputYs.back()); + const float angle2 = getAngle(mSampledInputXs.back(), mSampledInputYs.back(), + mSampledInputXs[size - 2], mSampledInputYs[size - 2]); const float angleDiff = getAngleDiff(angle1, angle2); - // Skip straight - if (nearest > STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD - && distPrev < baseSampleRate * STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE - && angleDiff < STRAIGHT_ANGLE_THRESHOLD) { - score += STRAIGHT_SKIP_SCORE; - } + // Save corner if (distPrev > baseSampleRate * CORNER_CHECK_DISTANCE_THRESHOLD_SCALE - && angleDiff > CORNER_ANGLE_THRESHOLD) { + && (sumAngle > CORNER_SUM_ANGLE_THRESHOLD || angleDiff > CORNER_ANGLE_THRESHOLD)) { score += CORNER_SCORE; } return score; @@ -397,64 +455,46 @@ float ProximityInfoState::getPointScore( // Sampling touch point and pushing information to vectors. // Returning if previous point is popped or not. -bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeChar, int x, int y, - const int time, const bool sample, const bool isLastPoint, +bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeCodePoint, int x, int y, + const int time, const bool sample, const bool isLastPoint, const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances, const NearKeysDistanceMap *const prevNearKeysDistances, const NearKeysDistanceMap *const prevPrevNearKeysDistances) { - static const float LAST_POINT_SKIP_DISTANCE_SCALE = 0.25f; + static const int LAST_POINT_SKIP_DISTANCE_SCALE = 4; - size_t size = mInputXs.size(); + size_t size = mSampledInputXs.size(); bool popped = false; - if (nodeChar < 0 && sample) { + if (nodeCodePoint < 0 && sample) { const float nearest = updateNearKeysDistances(x, y, currentNearKeysDistances); - const float score = getPointScore(x, y, time, isLastPoint, nearest, + const float score = getPointScore(x, y, time, isLastPoint, nearest, sumAngle, currentNearKeysDistances, prevNearKeysDistances, prevPrevNearKeysDistances); if (score < 0) { // Pop previous point because it would be useless. popInputData(); - size = mInputXs.size(); + size = mSampledInputXs.size(); popped = true; } else { popped = false; } // Check if the last point should be skipped. - if (isLastPoint) { - if (size > 0 && getDistanceFloat(x, y, mInputXs.back(), mInputYs.back()) - < mProximityInfo->getMostCommonKeyWidth() * LAST_POINT_SKIP_DISTANCE_SCALE) { + if (isLastPoint && size > 0) { + if (getDistanceInt(x, y, mSampledInputXs.back(), mSampledInputYs.back()) + * LAST_POINT_SKIP_DISTANCE_SCALE < mProximityInfo->getMostCommonKeyWidth()) { + // This point is not used because it's too close to the previous point. if (DEBUG_GEO_FULL) { - AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %f, " - "width = %f", size, x, y, mInputXs.back(), mInputYs.back(), - getDistanceFloat(x, y, mInputXs.back(), mInputYs.back()), + AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, " + "width = %d", size, x, y, mSampledInputXs.back(), mSampledInputYs.back(), + getDistanceInt(x, y, mSampledInputXs.back(), mSampledInputYs.back()), mProximityInfo->getMostCommonKeyWidth() - * LAST_POINT_SKIP_DISTANCE_SCALE); + / LAST_POINT_SKIP_DISTANCE_SCALE); } return popped; - } else if (size > 1) { - int minChar = 0; - float minDist = mMaxPointToKeyLength; - for (NearKeysDistanceMap::const_iterator it = currentNearKeysDistances->begin(); - it != currentNearKeysDistances->end(); ++it) { - if (minDist > it->second) { - minChar = it->first; - minDist = it->second; - } - } - NearKeysDistanceMap::const_iterator itPP = - prevNearKeysDistances->find(minChar); - if (itPP != prevNearKeysDistances->end() && minDist > itPP->second) { - if (DEBUG_GEO_FULL) { - AKLOGI("p1: char = %c, minDist = %f, prevNear key minDist = %f", - minChar, itPP->second, minDist); - } - return popped; - } } } } - if (nodeChar >= 0 && (x < 0 || y < 0)) { - const int keyId = mProximityInfo->getKeyIndexOf(nodeChar); + if (nodeCodePoint >= 0 && (x < 0 || y < 0)) { + const int keyId = mProximityInfo->getKeyIndexOf(nodeCodePoint); if (keyId >= 0) { x = mProximityInfo->getKeyCenterXOfKeyIdG(keyId); y = mProximityInfo->getKeyCenterYOfKeyIdG(keyId); @@ -464,12 +504,13 @@ bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeChar // Pushing point information. if (size > 0) { mLengthCache.push_back( - mLengthCache.back() + getDistanceInt(x, y, mInputXs.back(), mInputYs.back())); + mLengthCache.back() + getDistanceInt( + x, y, mSampledInputXs.back(), mSampledInputYs.back())); } else { mLengthCache.push_back(0); } - mInputXs.push_back(x); - mInputYs.push_back(y); + mSampledInputXs.push_back(x); + mSampledInputYs.push_back(y); mTimes.push_back(time); mInputIndice.push_back(inputIndex); if (DEBUG_GEO_FULL) { @@ -487,7 +528,7 @@ float ProximityInfoState::calculateNormalizedSquaredDistance( if (!mProximityInfo->hasSweetSpotData(keyIndex)) { return NOT_A_DISTANCE_FLOAT; } - if (NOT_A_COORDINATE == mInputXs[inputIndex]) { + if (NOT_A_COORDINATE == mSampledInputXs[inputIndex]) { return NOT_A_DISTANCE_FLOAT; } const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter( @@ -497,28 +538,98 @@ float ProximityInfoState::calculateNormalizedSquaredDistance( } int ProximityInfoState::getDuration(const int index) const { - if (index >= 0 && index < mInputSize - 1) { + if (index >= 0 && index < mSampledInputSize - 1) { return mTimes[index + 1] - mTimes[index]; } return 0; } -float ProximityInfoState::getPointToKeyLength(const int inputIndex, const int codePoint, - const float scale) const { +float ProximityInfoState::getPointToKeyLength(const int inputIndex, const int codePoint) const { const int keyId = mProximityInfo->getKeyIndexOf(codePoint); if (keyId != NOT_AN_INDEX) { const int index = inputIndex * mProximityInfo->getKeyCount() + keyId; - return min(mDistanceCache[index] * scale, mMaxPointToKeyLength); + return min(mDistanceCache[index], mMaxPointToKeyLength); } - if (isSkippableChar(codePoint)) { + if (isSkippableCodePoint(codePoint)) { return 0.0f; } // If the char is not a key on the keyboard then return the max length. return MAX_POINT_TO_KEY_LENGTH; } +float ProximityInfoState::getPointToKeyByIdLength(const int inputIndex, const int keyId) const { + if (keyId != NOT_AN_INDEX) { + const int index = inputIndex * mProximityInfo->getKeyCount() + keyId; + return min(mDistanceCache[index], mMaxPointToKeyLength); + } + // If the char is not a key on the keyboard then return the max length. + return static_cast<float>(MAX_POINT_TO_KEY_LENGTH); +} + +// 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. +// proximityIndex is a pointer to the variable where getMatchedProximityId returns the index of c +// in the proximity chars of the input index. +// 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. +ProximityType ProximityInfoState::getMatchedProximityId(const int index, const int c, + const bool checkProximityChars, int *proximityIndex) const { + const int *currentCodePoints = getProximityCodePointsAt(index); + const int firstCodePoint = currentCodePoints[0]; + const int 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 (firstCodePoint == baseLowerC || firstCodePoint == c) { + return EQUIVALENT_CHAR; + } + + if (!checkProximityChars) 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(firstCodePoint) == baseLowerC) { + return NEAR_PROXIMITY_CHAR; + } + + // Not an exact nor an accent-alike match: search the list of close keys + int j = 1; + while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL + && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { + const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == c); + if (matched) { + if (proximityIndex) { + *proximityIndex = j; + } + return NEAR_PROXIMITY_CHAR; + } + ++j; + } + if (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL + && currentCodePoints[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { + ++j; + while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL + && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { + const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == c); + if (matched) { + if (proximityIndex) { + *proximityIndex = j; + } + return ADDITIONAL_PROXIMITY_CHAR; + } + ++j; + } + } + // Was not included, signal this as an unrelated character. + return UNRELATED_CHAR; +} + int ProximityInfoState::getSpaceY() const { - const int keyId = mProximityInfo->getKeyIndexOf(' '); + const int keyId = mProximityInfo->getKeyIndexOf(KEYCODE_SPACE); return mProximityInfo->getKeyCenterYOfKeyIdG(keyId); } @@ -526,20 +637,21 @@ float ProximityInfoState::calculateSquaredDistanceFromSweetSpotCenter( const int keyIndex, const int inputIndex) const { const float sweetSpotCenterX = mProximityInfo->getSweetSpotCenterXAt(keyIndex); const float sweetSpotCenterY = mProximityInfo->getSweetSpotCenterYAt(keyIndex); - const float inputX = static_cast<float>(mInputXs[inputIndex]); - const float inputY = static_cast<float>(mInputYs[inputIndex]); + const float inputX = static_cast<float>(mSampledInputXs[inputIndex]); + const float inputY = static_cast<float>(mSampledInputYs[inputIndex]); return square(inputX - sweetSpotCenterX) + square(inputY - sweetSpotCenterY); } // Puts possible characters into filter and returns new filter size. int32_t ProximityInfoState::getAllPossibleChars( const size_t index, int32_t *const filter, const int32_t filterSize) const { - if (index >= mInputXs.size()) { + if (index >= mSampledInputXs.size()) { return filterSize; } int newFilterSize = filterSize; - for (int j = 0; j < mProximityInfo->getKeyCount(); ++j) { - if (mNearKeysVector[index].test(j)) { + const int keyCount = mProximityInfo->getKeyCount(); + for (int j = 0; j < keyCount; ++j) { + if (mSearchKeysVector[index].test(j)) { const int32_t keyCodePoint = mProximityInfo->getCodePointOf(j); bool insert = true; // TODO: Avoid linear search @@ -557,12 +669,402 @@ int32_t ProximityInfoState::getAllPossibleChars( return newFilterSize; } +bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const { + ASSERT(keyId >= 0); + ASSERT(index >= 0 && index < mSampledInputSize); + return mSearchKeysVector[index].test(keyId); +} + void ProximityInfoState::popInputData() { - mInputXs.pop_back(); - mInputYs.pop_back(); + mSampledInputXs.pop_back(); + mSampledInputYs.pop_back(); mTimes.pop_back(); mLengthCache.pop_back(); mInputIndice.pop_back(); } +float ProximityInfoState::getDirection(const int index0, const int index1) const { + if (index0 < 0 || index0 > mSampledInputSize - 1) { + return 0.0f; + } + if (index1 < 0 || index1 > mSampledInputSize - 1) { + return 0.0f; + } + const int x1 = mSampledInputXs[index0]; + const int y1 = mSampledInputYs[index0]; + const int x2 = mSampledInputXs[index1]; + const int y2 = mSampledInputYs[index1]; + return getAngle(x1, y1, x2, y2); +} + +float ProximityInfoState::getPointAngle(const int index) const { + if (index <= 0 || index >= mSampledInputSize - 1) { + return 0.0f; + } + const float previousDirection = getDirection(index - 1, index); + const float nextDirection = getDirection(index, index + 1); + const float directionDiff = getAngleDiff(previousDirection, nextDirection); + return directionDiff; +} + +float ProximityInfoState::getPointsAngle( + const int index0, const int index1, const int index2) const { + if (index0 < 0 || index0 > mSampledInputSize - 1) { + return 0.0f; + } + if (index1 < 0 || index1 > mSampledInputSize - 1) { + return 0.0f; + } + if (index2 < 0 || index2 > mSampledInputSize - 1) { + return 0.0f; + } + const float previousDirection = getDirection(index0, index1); + const float nextDirection = getDirection(index1, index2); + return getAngleDiff(previousDirection, nextDirection); +} + +float ProximityInfoState::getLineToKeyDistance( + const int from, const int to, const int keyId, const bool extend) const { + if (from < 0 || from > mSampledInputSize - 1) { + return 0.0f; + } + if (to < 0 || to > mSampledInputSize - 1) { + return 0.0f; + } + const int x0 = mSampledInputXs[from]; + const int y0 = mSampledInputYs[from]; + const int x1 = mSampledInputXs[to]; + const int y1 = mSampledInputYs[to]; + + const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId); + const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId); + + return pointToLineSegSquaredDistanceFloat(keyX, keyY, x0, y0, x1, y1, extend); +} + +// Updates probabilities of aligning to some keys and skipping. +// Word suggestion should be based on this probabilities. +void ProximityInfoState::updateAlignPointProbabilities(const int start) { + static const float MIN_PROBABILITY = 0.000001f; + static const float MAX_SKIP_PROBABILITY = 0.95f; + static const float SKIP_FIRST_POINT_PROBABILITY = 0.01f; + static const float SKIP_LAST_POINT_PROBABILITY = 0.1f; + static const float MIN_SPEED_RATE_FOR_SKIP_PROBABILITY = 0.15f; + static const float SPEED_WEIGHT_FOR_SKIP_PROBABILITY = 0.9f; + static const float SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY = 0.6f; + static const float NEAREST_DISTANCE_WEIGHT = 0.5f; + static const float NEAREST_DISTANCE_BIAS = 0.5f; + static const float NEAREST_DISTANCE_WEIGHT_FOR_LAST = 0.6f; + static const float NEAREST_DISTANCE_BIAS_FOR_LAST = 0.4f; + + static const float ANGLE_WEIGHT = 0.90f; + static const float DEEP_CORNER_ANGLE_THRESHOLD = M_PI_F * 60.0f / 180.0f; + static const float SKIP_DEEP_CORNER_PROBABILITY = 0.1f; + static const float CORNER_ANGLE_THRESHOLD = M_PI_F * 30.0f / 180.0f; + static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F * 15.0f / 180.0f; + static const float SKIP_CORNER_PROBABILITY = 0.4f; + static const float SPEED_MARGIN = 0.1f; + static const float CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION = 0.0f; + + const int keyCount = mProximityInfo->getKeyCount(); + mCharProbabilities.resize(mSampledInputSize); + // Calculates probabilities of using a point as a correlated point with the character + // for each point. + for (int i = start; i < mSampledInputSize; ++i) { + mCharProbabilities[i].clear(); + // First, calculates skip probability. Starts form MIN_SKIP_PROBABILITY. + // Note that all values that are multiplied to this probability should be in [0.0, 1.0]; + float skipProbability = MAX_SKIP_PROBABILITY; + + const float currentAngle = getPointAngle(i); + const float relativeSpeed = getRelativeSpeed(i); + + float nearestKeyDistance = static_cast<float>(MAX_POINT_TO_KEY_LENGTH); + for (int j = 0; j < keyCount; ++j) { + if (mNearKeysVector[i].test(j)) { + const float distance = getPointToKeyByIdLength(i, j); + if (distance < nearestKeyDistance) { + nearestKeyDistance = distance; + } + } + } + + if (i == 0) { + skipProbability *= min(1.0f, nearestKeyDistance * NEAREST_DISTANCE_WEIGHT + + NEAREST_DISTANCE_BIAS); + // Promote the first point + skipProbability *= SKIP_FIRST_POINT_PROBABILITY; + } else if (i == mSampledInputSize - 1) { + skipProbability *= min(1.0f, nearestKeyDistance * NEAREST_DISTANCE_WEIGHT_FOR_LAST + + NEAREST_DISTANCE_BIAS_FOR_LAST); + // Promote the last point + skipProbability *= SKIP_LAST_POINT_PROBABILITY; + } else { + // If the current speed is relatively slower than adjacent keys, we promote this point. + if (getRelativeSpeed(i - 1) - SPEED_MARGIN > relativeSpeed + && relativeSpeed < getRelativeSpeed(i + 1) - SPEED_MARGIN) { + if (currentAngle < CORNER_ANGLE_THRESHOLD) { + skipProbability *= min(1.0f, relativeSpeed + * SLOW_STRAIGHT_WEIGHT_FOR_SKIP_PROBABILITY); + } else { + // If the angle is small enough, we promote this point more. (e.g. pit vs put) + skipProbability *= min(1.0f, relativeSpeed * SPEED_WEIGHT_FOR_SKIP_PROBABILITY + + MIN_SPEED_RATE_FOR_SKIP_PROBABILITY); + } + } + + skipProbability *= min(1.0f, relativeSpeed * nearestKeyDistance * + NEAREST_DISTANCE_WEIGHT + NEAREST_DISTANCE_BIAS); + + // Adjusts skip probability by a rate depending on angle. + // ANGLE_RATE of skipProbability is adjusted by current angle. + skipProbability *= (M_PI_F - currentAngle) / M_PI_F * ANGLE_WEIGHT + + (1.0f - ANGLE_WEIGHT); + if (currentAngle > DEEP_CORNER_ANGLE_THRESHOLD) { + skipProbability *= SKIP_DEEP_CORNER_PROBABILITY; + } + // We assume the angle of this point is the angle for point[i], point[i - 2] + // and point[i - 3]. The reason why we don't use the angle for point[i], point[i - 1] + // and point[i - 2] is this angle can be more affected by the noise. + const float prevAngle = getPointsAngle(i, i - 2, i - 3); + if (i >= 3 && prevAngle < STRAIGHT_ANGLE_THRESHOLD + && currentAngle > CORNER_ANGLE_THRESHOLD) { + skipProbability *= SKIP_CORNER_PROBABILITY; + } + } + + // probabilities must be in [0.0, MAX_SKIP_PROBABILITY]; + ASSERT(skipProbability >= 0.0f); + ASSERT(skipProbability <= MAX_SKIP_PROBABILITY); + mCharProbabilities[i][NOT_AN_INDEX] = skipProbability; + + // Second, calculates key probabilities by dividing the rest probability + // (1.0f - skipProbability). + const float inputCharProbability = 1.0f - skipProbability; + + // TODO: The variance is critical for accuracy; thus, adjusting these parameter by machine + // learning or something would be efficient. + static const float SPEEDxANGLE_WEIGHT_FOR_STANDARD_DIVIATION = 0.3f; + static const float MAX_SPEEDxANGLE_RATE_FOR_STANDERD_DIVIATION = 0.25f; + static const float SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DIVIATION = 0.5f; + static const float MAX_SPEEDxNEAREST_RATE_FOR_STANDERD_DIVIATION = 0.15f; + static const float MIN_STANDERD_DIVIATION = 0.37f; + + const float speedxAngleRate = min(relativeSpeed * currentAngle / M_PI_F + * SPEEDxANGLE_WEIGHT_FOR_STANDARD_DIVIATION, + MAX_SPEEDxANGLE_RATE_FOR_STANDERD_DIVIATION); + const float speedxNearestKeyDistanceRate = min(relativeSpeed * nearestKeyDistance + * SPEEDxNEAREST_WEIGHT_FOR_STANDARD_DIVIATION, + MAX_SPEEDxNEAREST_RATE_FOR_STANDERD_DIVIATION); + const float sigma = speedxAngleRate + speedxNearestKeyDistanceRate + MIN_STANDERD_DIVIATION; + + NormalDistribution distribution(CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION, sigma); + static const float PREV_DISTANCE_WEIGHT = 0.5f; + static const float NEXT_DISTANCE_WEIGHT = 0.6f; + // Summing up probability densities of all near keys. + float sumOfProbabilityDensities = 0.0f; + for (int j = 0; j < keyCount; ++j) { + if (mNearKeysVector[i].test(j)) { + float distance = sqrtf(getPointToKeyByIdLength(i, j)); + if (i == 0 && i != mSampledInputSize - 1) { + // For the first point, weighted average of distances from first point and the + // next point to the key is used as a point to key distance. + const float nextDistance = sqrtf(getPointToKeyByIdLength(i + 1, j)); + if (nextDistance < distance) { + // The distance of the first point tends to bigger than continuing + // points because the first touch by the user can be sloppy. + // So we promote the first point if the distance of that point is larger + // than the distance of the next point. + distance = (distance + nextDistance * NEXT_DISTANCE_WEIGHT) + / (1.0f + NEXT_DISTANCE_WEIGHT); + } + } else if (i != 0 && i == mSampledInputSize - 1) { + // For the first point, weighted average of distances from last point and + // the previous point to the key is used as a point to key distance. + const float previousDistance = sqrtf(getPointToKeyByIdLength(i - 1, j)); + if (previousDistance < distance) { + // The distance of the last point tends to bigger than continuing points + // because the last touch by the user can be sloppy. So we promote the + // last point if the distance of that point is larger than the distance of + // the previous point. + distance = (distance + previousDistance * PREV_DISTANCE_WEIGHT) + / (1.0f + PREV_DISTANCE_WEIGHT); + } + } + // TODO: Promote the first point when the extended line from the next input is near + // from a key. Also, promote the last point as well. + sumOfProbabilityDensities += distribution.getProbabilityDensity(distance); + } + } + + // Split the probability of an input point to keys that are close to the input point. + for (int j = 0; j < keyCount; ++j) { + if (mNearKeysVector[i].test(j)) { + float distance = sqrtf(getPointToKeyByIdLength(i, j)); + if (i == 0 && i != mSampledInputSize - 1) { + // For the first point, weighted average of distances from the first point and + // the next point to the key is used as a point to key distance. + const float prevDistance = sqrtf(getPointToKeyByIdLength(i + 1, j)); + if (prevDistance < distance) { + distance = (distance + prevDistance * NEXT_DISTANCE_WEIGHT) + / (1.0f + NEXT_DISTANCE_WEIGHT); + } + } else if (i != 0 && i == mSampledInputSize - 1) { + // For the first point, weighted average of distances from last point and + // the previous point to the key is used as a point to key distance. + const float prevDistance = sqrtf(getPointToKeyByIdLength(i - 1, j)); + if (prevDistance < distance) { + distance = (distance + prevDistance * PREV_DISTANCE_WEIGHT) + / (1.0f + PREV_DISTANCE_WEIGHT); + } + } + const float probabilityDensity = distribution.getProbabilityDensity(distance); + const float probability = inputCharProbability * probabilityDensity + / sumOfProbabilityDensities; + mCharProbabilities[i][j] = probability; + } + } + } + + + if (DEBUG_POINTS_PROBABILITY) { + for (int i = 0; i < mSampledInputSize; ++i) { + std::stringstream sstream; + sstream << i << ", "; + sstream << "(" << mSampledInputXs[i] << ", " << mSampledInputYs[i] << "), "; + sstream << "Speed: "<< getRelativeSpeed(i) << ", "; + sstream << "Angle: "<< getPointAngle(i) << ", \n"; + + for (hash_map_compat<int, float>::iterator it = mCharProbabilities[i].begin(); + it != mCharProbabilities[i].end(); ++it) { + if (it->first == NOT_AN_INDEX) { + sstream << it->first + << "(skip):" + << it->second + << "\n"; + } else { + sstream << it->first + << "(" + << static_cast<char>(mProximityInfo->getCodePointOf(it->first)) + << "):" + << it->second + << "\n"; + } + } + AKLOGI("%s", sstream.str().c_str()); + } + } + + // Decrease key probabilities of points which don't have the highest probability of that key + // among nearby points. Probabilities of the first point and the last point are not suppressed. + for (int i = max(start, 1); i < mSampledInputSize; ++i) { + for (int j = i + 1; j < mSampledInputSize; ++j) { + if (!suppressCharProbabilities(i, j)) { + break; + } + } + for (int j = i - 1; j >= max(start, 0); --j) { + if (!suppressCharProbabilities(i, j)) { + break; + } + } + } + + // Converting from raw probabilities to log probabilities to calculate spatial distance. + for (int i = start; i < mSampledInputSize; ++i) { + for (int j = 0; j < keyCount; ++j) { + hash_map_compat<int, float>::iterator it = mCharProbabilities[i].find(j); + if (it == mCharProbabilities[i].end()){ + mNearKeysVector[i].reset(j); + } else if(it->second < MIN_PROBABILITY) { + // Erases from near keys vector because it has very low probability. + mNearKeysVector[i].reset(j); + mCharProbabilities[i].erase(j); + } else { + it->second = -logf(it->second); + } + } + mCharProbabilities[i][NOT_AN_INDEX] = -logf(mCharProbabilities[i][NOT_AN_INDEX]); + } +} + +// Decreases char probabilities of index0 by checking probabilities of a near point (index1) and +// increases char probabilities of index1 by checking probabilities of index0. +bool ProximityInfoState::suppressCharProbabilities(const int index0, const int index1) { + ASSERT(0 <= index0 && index0 < mSampledInputSize); + ASSERT(0 <= index1 && index1 < mSampledInputSize); + + static const float SUPPRESSION_LENGTH_WEIGHT = 1.5f; + static const float MIN_SUPPRESSION_RATE = 0.1f; + static const float SUPPRESSION_WEIGHT = 0.5f; + static const float SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN = 0.1f; + static const float SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN = 0.3f; + + const float keyWidthFloat = static_cast<float>(mProximityInfo->getMostCommonKeyWidth()); + const float diff = fabsf(static_cast<float>(mLengthCache[index0] - mLengthCache[index1])); + if (diff > keyWidthFloat * SUPPRESSION_LENGTH_WEIGHT) { + return false; + } + const float suppressionRate = MIN_SUPPRESSION_RATE + + diff / keyWidthFloat / SUPPRESSION_LENGTH_WEIGHT * SUPPRESSION_WEIGHT; + for (hash_map_compat<int, float>::iterator it = mCharProbabilities[index0].begin(); + it != mCharProbabilities[index0].end(); ++it) { + hash_map_compat<int, float>::iterator it2 = mCharProbabilities[index1].find(it->first); + if (it2 != mCharProbabilities[index1].end() && it->second < it2->second) { + const float newProbability = it->second * suppressionRate; + const float suppression = it->second - newProbability; + it->second = newProbability; + // mCharProbabilities[index0][NOT_AN_INDEX] is the probability of skipping this point. + mCharProbabilities[index0][NOT_AN_INDEX] += suppression; + + // Add the probability of the same key nearby index1 + const float probabilityGain = min(suppression * SUPPRESSION_WEIGHT_FOR_PROBABILITY_GAIN, + mCharProbabilities[index1][NOT_AN_INDEX] + * SKIP_PROBABALITY_WEIGHT_FOR_PROBABILITY_GAIN); + it2->second += probabilityGain; + mCharProbabilities[index1][NOT_AN_INDEX] -= probabilityGain; + } + } + return true; +} + +// Get a word that is detected by tracing highest probability sequence into codePointBuf and +// returns probability of generating the word. +float ProximityInfoState::getHighestProbabilitySequence(int *const codePointBuf) const { + static const float DEMOTION_LOG_PROBABILITY = 0.3f; + int index = 0; + float sumLogProbability = 0.0f; + // TODO: Current implementation is greedy algorithm. DP would be efficient for many cases. + for (int i = 0; i < mSampledInputSize && index < MAX_WORD_LENGTH_INTERNAL - 1; ++i) { + float minLogProbability = static_cast<float>(MAX_POINT_TO_KEY_LENGTH); + int character = NOT_AN_INDEX; + for (hash_map_compat<int, float>::const_iterator it = mCharProbabilities[i].begin(); + it != mCharProbabilities[i].end(); ++it) { + const float logProbability = (it->first != NOT_AN_INDEX) + ? it->second + DEMOTION_LOG_PROBABILITY : it->second; + if (logProbability < minLogProbability) { + minLogProbability = logProbability; + character = it->first; + } + } + if (character != NOT_AN_INDEX) { + codePointBuf[index] = mProximityInfo->getCodePointOf(character); + index++; + } + sumLogProbability += minLogProbability; + } + codePointBuf[index] = '\0'; + return sumLogProbability; +} + +// Returns a probability of mapping index to keyIndex. +float ProximityInfoState::getProbability(const int index, const int keyIndex) const { + ASSERT(0 <= index && index < mSampledInputSize); + hash_map_compat<int, float>::const_iterator it = mCharProbabilities[index].find(keyIndex); + if (it != mCharProbabilities[index].end()) { + return it->second; + } + return static_cast<float>(MAX_POINT_TO_KEY_LENGTH); +} + } // namespace latinime diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h index c1ec76c38..79dd5be53 100644 --- a/native/jni/src/proximity_info_state.h +++ b/native/jni/src/proximity_info_state.h @@ -43,36 +43,38 @@ class ProximityInfoState { // Defined in proximity_info_state.cpp // ///////////////////////////////////////// void initInputParams(const int pointerId, const float maxPointToKeyLength, - const ProximityInfo *proximityInfo, const int32_t *const inputCodes, + const ProximityInfo *proximityInfo, const int *const inputCodes, const int inputSize, const int *xCoordinates, const int *yCoordinates, const int *const times, const int *const pointerIds, const bool isGeometric); ///////////////////////////////////////// // Defined here // ///////////////////////////////////////// - ProximityInfoState() + AK_FORCE_INLINE ProximityInfoState() : mProximityInfo(0), mMaxPointToKeyLength(0), mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(), mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0), - mIsContinuationPossible(false), mInputXs(), mInputYs(), mTimes(), mInputIndice(), - mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mNearKeysVector(), - mTouchPositionCorrectionEnabled(false), mInputSize(0) { + mIsContinuationPossible(false), mSampledInputXs(), mSampledInputYs(), mTimes(), + mInputIndice(), mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mDirections(), + mCharProbabilities(), mNearKeysVector(), mSearchKeysVector(), + mTouchPositionCorrectionEnabled(false), mSampledInputSize(0) { memset(mInputCodes, 0, sizeof(mInputCodes)); memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances)); memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); } - virtual ~ProximityInfoState() {} + // Non virtual inline destructor -- never inherit this class + AK_FORCE_INLINE ~ProximityInfoState() {} - inline unsigned short getPrimaryCharAt(const int index) const { - return getProximityCharsAt(index)[0]; + inline int getPrimaryCodePointAt(const int index) const { + return getProximityCodePointsAt(index)[0]; } - inline bool existsCharInProximityAt(const int index, const int c) const { - const int *chars = getProximityCharsAt(index); + AK_FORCE_INLINE bool existsCodePointInProximityAt(const int index, const int c) const { + const int *codePoints = getProximityCodePointsAt(index); int i = 0; - while (chars[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE_INTERNAL) { - if (chars[i++] == c) { + while (codePoints[i] > 0 && i < MAX_PROXIMITY_CHARS_SIZE_INTERNAL) { + if (codePoints[i++] == c) { return true; } } @@ -80,91 +82,27 @@ class ProximityInfoState { } inline bool existsAdjacentProximityChars(const int index) const { - if (index < 0 || index >= mInputSize) return false; - const int currentChar = getPrimaryCharAt(index); + if (index < 0 || index >= mSampledInputSize) return false; + const int currentCodePoint = getPrimaryCodePointAt(index); const int leftIndex = index - 1; - if (leftIndex >= 0 && existsCharInProximityAt(leftIndex, currentChar)) { + if (leftIndex >= 0 && existsCodePointInProximityAt(leftIndex, currentCodePoint)) { return true; } const int rightIndex = index + 1; - if (rightIndex < mInputSize && existsCharInProximityAt(rightIndex, currentChar)) { + if (rightIndex < mSampledInputSize + && existsCodePointInProximityAt(rightIndex, currentCodePoint)) { return true; } 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. - // proximityIndex is a pointer to the variable where getMatchedProximityId returns - // the index of c in the proximity chars of the input index. - // 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 ProximityType getMatchedProximityId(const int index, - const unsigned short c, const bool checkProximityChars, int *proximityIndex = 0) const { - const int *currentChars = getProximityCharsAt(index); - const int firstChar = currentChars[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 (firstChar == baseLowerC || firstChar == c) { - return EQUIVALENT_CHAR; - } - - if (!checkProximityChars) 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(firstChar) == baseLowerC) - return NEAR_PROXIMITY_CHAR; - - // Not an exact nor an accent-alike match: search the list of close keys - int j = 1; - while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL - && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { - const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c); - if (matched) { - if (proximityIndex) { - *proximityIndex = j; - } - return NEAR_PROXIMITY_CHAR; - } - ++j; - } - if (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL - && currentChars[j] == ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { - ++j; - while (j < MAX_PROXIMITY_CHARS_SIZE_INTERNAL - && currentChars[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) { - const bool matched = (currentChars[j] == baseLowerC || currentChars[j] == c); - if (matched) { - if (proximityIndex) { - *proximityIndex = j; - } - return ADDITIONAL_PROXIMITY_CHAR; - } - ++j; - } - } - - // Was not included, signal this as an unrelated character. - return UNRELATED_CHAR; - } - inline int getNormalizedSquaredDistance( const int inputIndex, const int proximityIndex) const { return mNormalizedSquaredDistances[ inputIndex * MAX_PROXIMITY_CHARS_SIZE_INTERNAL + proximityIndex]; } - inline const unsigned short *getPrimaryInputWord() const { + inline const int *getPrimaryInputWord() const { return mPrimaryInputWord; } @@ -172,13 +110,13 @@ class ProximityInfoState { return mTouchPositionCorrectionEnabled; } - inline bool sameAsTyped(const unsigned short *word, int length) const { - if (length != mInputSize) { + inline bool sameAsTyped(const int *word, int length) const { + if (length != mSampledInputSize) { return false; } const int *inputCodes = mInputCodes; while (length--) { - if (static_cast<unsigned int>(*inputCodes) != static_cast<unsigned int>(*word)) { + if (*inputCodes != *word) { return false; } inputCodes += MAX_PROXIMITY_CHARS_SIZE_INTERNAL; @@ -190,19 +128,19 @@ class ProximityInfoState { int getDuration(const int index) const; bool isUsed() const { - return mInputSize > 0; + return mSampledInputSize > 0; } uint32_t size() const { - return mInputSize; + return mSampledInputSize; } int getInputX(const int index) const { - return mInputXs[index]; + return mSampledInputXs[index]; } int getInputY(const int index) const { - return mInputYs[index]; + return mSampledInputYs[index]; } int getLengthCache(const int index) const { @@ -213,7 +151,11 @@ class ProximityInfoState { return mIsContinuationPossible; } - float getPointToKeyLength(const int inputIndex, const int charCode, const float scale) const; + float getPointToKeyLength(const int inputIndex, const int charCode) const; + float getPointToKeyByIdLength(const int inputIndex, const int keyId) const; + + ProximityType getMatchedProximityId(const int index, const int c, + const bool checkProximityChars, int *proximityIndex = 0) const; int getSpaceY() const; @@ -223,6 +165,25 @@ class ProximityInfoState { float getRelativeSpeed(const int index) const { return mRelativeSpeeds[index]; } + + float getDirection(const int index) const { + return mDirections[index]; + } + // get xy direction + float getDirection(const int x, const int y) const; + + float getPointAngle(const int index) const; + // Returns angle of three points. x, y, and z are indices. + float getPointsAngle(const int index0, const int index1, const int index2) const; + + float getHighestProbabilitySequence(int *const codePointBuf) const; + + float getProbability(const int index, const int charCode) const; + + float getLineToKeyDistance( + const int from, const int to, const int keyId, const bool extend) const; + + bool isKeyInSerchKeysAfterIndex(const int index, const int keyId) const; private: DISALLOW_COPY_AND_ASSIGN(ProximityInfoState); typedef hash_map_compat<int, float> NearKeysDistanceMap; @@ -234,8 +195,8 @@ class ProximityInfoState { float calculateSquaredDistanceFromSweetSpotCenter( const int keyIndex, const int inputIndex) const; - bool pushTouchPoint(const int inputIndex, const int nodeChar, int x, int y, const int time, - const bool sample, const bool isLastPoint, + bool pushTouchPoint(const int inputIndex, const int nodeCodePoint, int x, int y, const int time, + const bool sample, const bool isLastPoint, const float sumAngle, NearKeysDistanceMap *const currentNearKeysDistances, const NearKeysDistanceMap *const prevNearKeysDistances, const NearKeysDistanceMap *const prevPrevNearKeysDistances); @@ -245,10 +206,10 @@ class ProximityInfoState { inline float square(const float x) const { return x * x; } bool hasInputCoordinates() const { - return mInputXs.size() > 0 && mInputYs.size() > 0; + return mSampledInputXs.size() > 0 && mSampledInputYs.size() > 0; } - inline const int *getProximityCharsAt(const int index) const { + inline const int *getProximityCodePointsAt(const int index) const { return mInputCodes + (index * MAX_PROXIMITY_CHARS_SIZE_INTERNAL); } @@ -259,12 +220,16 @@ class ProximityInfoState { const NearKeysDistanceMap *const prevPrevNearKeysDistances) const; float getPointScore( const int x, const int y, const int time, const bool last, const float nearest, - const NearKeysDistanceMap *const currentNearKeysDistances, + const float sumAngle, const NearKeysDistanceMap *const currentNearKeysDistances, const NearKeysDistanceMap *const prevNearKeysDistances, const NearKeysDistanceMap *const prevPrevNearKeysDistances) const; bool checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates, const int *const yCoordinates, const int *const times); void popInputData(); + void updateAlignPointProbabilities(const int start); + bool suppressCharProbabilities(const int index1, const int index2); + void refreshRelativeSpeed(const int inputSize, const int *const xCoordinates, + const int *const yCoordinates, const int *const times, const int lastSavedInputSize); // const const ProximityInfo *mProximityInfo; @@ -279,19 +244,30 @@ class ProximityInfoState { int mGridWidth; bool mIsContinuationPossible; - std::vector<int> mInputXs; - std::vector<int> mInputYs; + std::vector<int> mSampledInputXs; + std::vector<int> mSampledInputYs; std::vector<int> mTimes; std::vector<int> mInputIndice; std::vector<float> mDistanceCache; std::vector<int> mLengthCache; std::vector<float> mRelativeSpeeds; + std::vector<float> mDirections; + // probabilities of skipping or mapping to a key for each point. + std::vector<hash_map_compat<int, float> > mCharProbabilities; + // The vector for the key code set which holds nearby keys for each sampled input point + // 1. Used to calculate the probability of the key + // 2. Used to calculate mSearchKeysVector std::vector<NearKeycodesSet> mNearKeysVector; + // The vector for the key code set which holds nearby keys of some trailing sampled input points + // for each sampled input point. These nearby keys contain the next characters which can be in + // the dictionary. Specifically, currently we are looking for keys nearby trailing sampled + // inputs including the current input point. + std::vector<NearKeycodesSet> mSearchKeysVector; bool mTouchPositionCorrectionEnabled; - int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL]; + int mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL]; int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL]; - int mInputSize; - unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL]; + int mSampledInputSize; + int mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL]; }; } // namespace latinime #endif // LATINIME_PROXIMITY_INFO_STATE_H diff --git a/native/jni/src/terminal_attributes.h b/native/jni/src/terminal_attributes.h index e72e7e3be..fed3c7251 100644 --- a/native/jni/src/terminal_attributes.h +++ b/native/jni/src/terminal_attributes.h @@ -43,18 +43,16 @@ class TerminalAttributes { return mHasNextShortcutTarget; } - // Gets the shortcut target itself as a uint16_t string. For parameters and return value + // Gets the shortcut target itself as an int string. For parameters and return value // see BinaryFormat::getWordAtAddress. - // TODO: make the output an uint32_t* to handle the whole unicode range. - inline int getNextShortcutTarget(const int maxDepth, uint16_t *outWord, int *outFreq) { + inline int getNextShortcutTarget(const int maxDepth, int *outWord, int *outFreq) { const int shortcutFlags = BinaryFormat::getFlagsAndForwardPointer(mDict, &mPos); - mHasNextShortcutTarget = - 0 != (shortcutFlags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT); + mHasNextShortcutTarget = 0 != (shortcutFlags & BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT); unsigned int i; for (i = 0; i < MAX_WORD_LENGTH_INTERNAL; ++i) { const int codePoint = BinaryFormat::getCodePointAndForwardPointer(mDict, &mPos); if (NOT_A_CODE_POINT == codePoint) break; - outWord[i] = (uint16_t)codePoint; + outWord[i] = codePoint; } *outFreq = BinaryFormat::getAttributeFrequencyFromFlags(shortcutFlags); return i; diff --git a/native/jni/src/unigram_dictionary.cpp b/native/jni/src/unigram_dictionary.cpp index e3649bd4b..def4a5bf8 100644 --- a/native/jni/src/unigram_dictionary.cpp +++ b/native/jni/src/unigram_dictionary.cpp @@ -41,14 +41,10 @@ const UnigramDictionary::digraph_t UnigramDictionary::FRENCH_LIGATURES_DIGRAPHS[ { 'o', 'e', 0x0153 } }; // U+0153 : LATIN SMALL LIGATURE OE // TODO: check the header -UnigramDictionary::UnigramDictionary(const uint8_t *const streamStart, int typedLetterMultiplier, - int fullWordMultiplier, int maxWordLength, int maxWords, const unsigned int flags) - : DICT_ROOT(streamStart), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), - TYPED_LETTER_MULTIPLIER(typedLetterMultiplier), FULL_WORD_MULTIPLIER(fullWordMultiplier), - // TODO : remove this variable. - ROOT_POS(0), - BYTES_IN_ONE_CHAR(sizeof(int)), - MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH), FLAGS(flags) { +UnigramDictionary::UnigramDictionary(const uint8_t *const streamStart, int maxWordLength, + int maxWords, const unsigned int flags) + : DICT_ROOT(streamStart), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), + ROOT_POS(0), MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH), FLAGS(flags) { if (DEBUG_DICT) { AKLOGI("UnigramDictionary - constructor"); } @@ -57,13 +53,12 @@ UnigramDictionary::UnigramDictionary(const uint8_t *const streamStart, int typed UnigramDictionary::~UnigramDictionary() { } -static inline unsigned int getCodesBufferSize(const int *codes, const int codesSize) { - return static_cast<unsigned int>(sizeof(*codes)) * codesSize; +static inline int getCodesBufferSize(const int *codes, const int codesSize) { + return sizeof(*codes) * codesSize; } -// TODO: This needs to take a const unsigned short* and not tinker with its contents -static inline void addWord(unsigned short *word, int length, int frequency, - WordsPriorityQueue *queue, int type) { +// TODO: This needs to take a const int* and not tinker with its contents +static void addWord(int *word, int length, int frequency, WordsPriorityQueue *queue, int type) { queue->push(frequency, word, length, type); } @@ -105,6 +100,9 @@ void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximit const int codesRemain, const int currentDepth, int *codesDest, Correction *correction, WordsPriorityQueuePool *queuePool, const digraph_t *const digraphs, const unsigned int digraphsSize) const { + assert(sizeof(codesDest[0]) == sizeof(codesSrc[0])); + assert(sizeof(xCoordinatesBuffer[0]) == sizeof(xcoordinates[0])); + assert(sizeof(yCoordinatesBuffer[0]) == sizeof(ycoordinates[0])); const int startIndex = static_cast<int>(codesDest - codesBuffer); if (currentDepth < MAX_DIGRAPH_SEARCH_DEPTH) { @@ -125,9 +123,8 @@ void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximit // 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); - codesDest[(i - 1) * (BYTES_IN_ONE_CHAR / sizeof(codesDest[0]))] = - replacementCodePoint; + memcpy(codesDest, codesSrc, i * sizeof(codesDest[0])); + codesDest[i - 1] = replacementCodePoint; getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, bigramMap, bigramFilter, useFullEditDistance, codesSrc + i + 1, @@ -137,7 +134,7 @@ void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximit // Copy the second char of the digraph in place, then continue processing on // the remaining part of the word. // In our example, after "pru" in the buffer copy the "e", and continue on "fen" - memcpy(codesDest + i, codesSrc + i, BYTES_IN_ONE_CHAR); + memcpy(codesDest + i, codesSrc + i, sizeof(codesDest[0])); getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, bigramMap, bigramFilter, useFullEditDistance, codesSrc + i, codesRemain - i, @@ -153,13 +150,13 @@ void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximit // 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; + const unsigned int remainingBytes = sizeof(codesDest[0]) * codesRemain; if (0 != remainingBytes) { memcpy(codesDest, codesSrc, remainingBytes); memcpy(&xCoordinatesBuffer[startIndex], &xcoordinates[codesBufferSize - codesRemain], - sizeof(int) * codesRemain); + sizeof(xCoordinatesBuffer[0]) * codesRemain); memcpy(&yCoordinatesBuffer[startIndex], &ycoordinates[codesBufferSize - codesRemain], - sizeof(int) * codesRemain); + sizeof(yCoordinatesBuffer[0]) * codesRemain); } getWordSuggestions(proximityInfo, xCoordinatesBuffer, yCoordinatesBuffer, codesBuffer, @@ -173,9 +170,7 @@ void UnigramDictionary::getWordWithDigraphSuggestionsRec(ProximityInfo *proximit int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, - const bool useFullEditDistance, unsigned short *outWords, int *frequencies, - int *outputTypes) const { - + const bool useFullEditDistance, int *outWords, int *frequencies, int *outputTypes) const { WordsPriorityQueuePool queuePool(MAX_WORDS, SUB_QUEUE_MAX_WORDS, MAX_WORD_LENGTH); queuePool.clearAll(); Correction masterCorrection; @@ -188,8 +183,7 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *x getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesSize, bigramMap, bigramFilter, useFullEditDistance, codes, codesSize, 0, codesBuffer, &masterCorrection, - &queuePool, GERMAN_UMLAUT_DIGRAPHS, - sizeof(GERMAN_UMLAUT_DIGRAPHS) / sizeof(GERMAN_UMLAUT_DIGRAPHS[0])); + &queuePool, GERMAN_UMLAUT_DIGRAPHS, NELEMS(GERMAN_UMLAUT_DIGRAPHS)); } else if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & FLAGS) { int codesBuffer[getCodesBufferSize(codes, codesSize)]; int xCoordinatesBuffer[codesSize]; @@ -197,8 +191,7 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *x getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates, codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesSize, bigramMap, bigramFilter, useFullEditDistance, codes, codesSize, 0, codesBuffer, &masterCorrection, - &queuePool, FRENCH_LIGATURES_DIGRAPHS, - sizeof(FRENCH_LIGATURES_DIGRAPHS) / sizeof(FRENCH_LIGATURES_DIGRAPHS[0])); + &queuePool, FRENCH_LIGATURES_DIGRAPHS, NELEMS(FRENCH_LIGATURES_DIGRAPHS)); } else { // Normal processing getWordSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, codesSize, bigramMap, bigramFilter, useFullEditDistance, &masterCorrection, &queuePool); @@ -222,7 +215,7 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *x AKLOGI("Returning %d words", suggestedWordsCount); /// Print the returned words for (int j = 0; j < suggestedWordsCount; ++j) { - short unsigned int *w = outWords + j * MAX_WORD_LENGTH; + int *w = outWords + j * MAX_WORD_LENGTH; char s[MAX_WORD_LENGTH]; for (int i = 0; i <= MAX_WORD_LENGTH; i++) s[i] = w[i]; (void)s; // To suppress compiler warning @@ -234,12 +227,11 @@ int UnigramDictionary::getSuggestions(ProximityInfo *proximityInfo, const int *x return suggestedWordsCount; } -void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, - const int *xcoordinates, const int *ycoordinates, const int *codes, - const int inputSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, - const bool useFullEditDistance, Correction *correction, - WordsPriorityQueuePool *queuePool) const { - +void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, + const int *ycoordinates, const int *codes, const int inputSize, + const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, + const bool useFullEditDistance, Correction *correction, WordsPriorityQueuePool *queuePool) + const { PROF_OPEN; PROF_START(0); PROF_END(0); @@ -288,7 +280,7 @@ void UnigramDictionary::getWordSuggestions(ProximityInfo *proximityInfo, if (queue->size() > 0) { WordsPriorityQueue::SuggestedWord *sw = queue->top(); const int score = sw->mScore; - const unsigned short *word = sw->mWord; + const int *word = sw->mWord; const int wordLength = sw->mWordLength; float ns = Correction::RankingAlgorithm::calcNormalizedScore( correction->getPrimaryInputWord(), i, word, wordLength, score); @@ -307,15 +299,13 @@ void UnigramDictionary::initSuggestions(ProximityInfo *proximityInfo, const int Correction *correction) const { if (DEBUG_DICT) { AKLOGI("initSuggest"); - DUMP_WORD_INT(codes, inputSize); + DUMP_WORD(codes, inputSize); } correction->initInputParams(proximityInfo, codes, inputSize, xCoordinates, yCoordinates); const int maxDepth = min(inputSize * MAX_DEPTH_MULTIPLIER, MAX_WORD_LENGTH); correction->initCorrection(proximityInfo, inputSize, maxDepth); } -static const char SPACE = ' '; - void UnigramDictionary::getOneWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, @@ -374,7 +364,7 @@ void UnigramDictionary::getSuggestionCandidates(const bool useFullEditDistance, } } -inline void UnigramDictionary::onTerminal(const int probability, +void UnigramDictionary::onTerminal(const int probability, const TerminalAttributes& terminalAttributes, Correction *correction, WordsPriorityQueuePool *queuePool, const bool addToMasterQueue, const int currentWordIndex) const { @@ -382,7 +372,7 @@ inline void UnigramDictionary::onTerminal(const int probability, const bool addToSubQueue = inputIndex < SUB_QUEUE_MAX_COUNT; int wordLength; - unsigned short *wordPointer; + int *wordPointer; if ((currentWordIndex == FIRST_WORD_INDEX) && addToMasterQueue) { WordsPriorityQueue *masterQueue = queuePool->getMasterQueue(); @@ -410,7 +400,7 @@ inline void UnigramDictionary::onTerminal(const int probability, // so that the insert order is protected inside the queue for words // with the same score. For the moment we use -1 to make sure the shortcut will // never be in front of the word. - uint16_t shortcutTarget[MAX_WORD_LENGTH_INTERNAL]; + int shortcutTarget[MAX_WORD_LENGTH_INTERNAL]; int shortcutFrequency; const int shortcutTargetStringLength = iterator.getNextShortcutTarget( MAX_WORD_LENGTH_INTERNAL, shortcutTarget, &shortcutFrequency); @@ -450,7 +440,7 @@ int UnigramDictionary::getSubStringSuggestion( const bool hasAutoCorrectionCandidate, const int currentWordIndex, const int inputWordStartPos, const int inputWordLength, const int outputWordStartPos, const bool isSpaceProximity, int *freqArray, - int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const { + int *wordLengthArray, int *outputWord, int *outputWordLength) const { if (inputWordLength > MULTIPLE_WORDS_SUGGESTION_MAX_WORD_LENGTH) { return FLAG_MULTIPLE_SUGGEST_ABORT; } @@ -493,13 +483,13 @@ int UnigramDictionary::getSubStringSuggestion( // TODO: Remove the safety net above // ////////////////////////////////////////////// - unsigned short *tempOutputWord = 0; + int *tempOutputWord = 0; int nextWordLength = 0; // TODO: Optimize init suggestion initSuggestions(proximityInfo, xcoordinates, ycoordinates, codes, inputSize, correction); - unsigned short word[MAX_WORD_LENGTH_INTERNAL]; + int word[MAX_WORD_LENGTH_INTERNAL]; int freq = getMostFrequentWordLike( inputWordStartPos, inputWordLength, correction, word); if (freq > 0) { @@ -570,7 +560,7 @@ int UnigramDictionary::getSubStringSuggestion( if (outputWordStartPos + nextWordLength >= MAX_WORD_LENGTH) { return FLAG_MULTIPLE_SUGGEST_SKIP; } - outputWord[tempOutputWordLength] = SPACE; + outputWord[tempOutputWordLength] = KEYCODE_SPACE; if (outputWordLength) { ++*outputWordLength; } @@ -598,7 +588,7 @@ void UnigramDictionary::getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, const bool useFullEditDistance, const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate, const int startInputPos, const int startWordIndex, const int outputWordLength, - int *freqArray, int *wordLengthArray, unsigned short *outputWord) const { + int *freqArray, int *wordLengthArray, int *outputWord) const { if (startWordIndex >= (MULTIPLE_WORDS_SUGGESTION_MAX_WORDS - 1)) { // Return if the last word index return; @@ -684,7 +674,7 @@ void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximit } // Allocating fixed length array on stack - unsigned short outputWord[MAX_WORD_LENGTH]; + int outputWord[MAX_WORD_LENGTH]; int freqArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS]; int wordLengthArray[MULTIPLE_WORDS_SUGGESTION_MAX_WORDS]; const int outputWordLength = 0; @@ -698,12 +688,11 @@ void UnigramDictionary::getSplitMultipleWordsSuggestions(ProximityInfo *proximit // Wrapper for getMostFrequentWordLikeInner, which matches it to the previous // interface. -inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex, - const int inputSize, Correction *correction, unsigned short *word) const { - uint16_t inWord[inputSize]; - +int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex, const int inputSize, + Correction *correction, int *word) const { + int inWord[inputSize]; for (int i = 0; i < inputSize; ++i) { - inWord[i] = (uint16_t)correction->getPrimaryCharAt(startInputIndex + i); + inWord[i] = correction->getPrimaryCodePointAt(startInputIndex + i); } return getMostFrequentWordLikeInner(inWord, inputSize, word); } @@ -721,14 +710,14 @@ inline int UnigramDictionary::getMostFrequentWordLike(const int startInputIndex, // In and out parameters may point to the same location. This function takes care // not to use any input parameters after it wrote into its outputs. static inline bool testCharGroupForContinuedLikeness(const uint8_t flags, - const uint8_t *const root, const int startPos, const uint16_t *const inWord, - const int startInputIndex, const int inputSize, int32_t *outNewWord, int *outInputIndex, + const uint8_t *const root, const int startPos, const int *const inWord, + const int startInputIndex, const int inputSize, int *outNewWord, int *outInputIndex, int *outPos) { const bool hasMultipleChars = (0 != (BinaryFormat::FLAG_HAS_MULTIPLE_CHARS & flags)); int pos = startPos; - int32_t codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos); - int32_t baseChar = toBaseLowerCase(codePoint); - const uint16_t wChar = toBaseLowerCase(inWord[startInputIndex]); + int codePoint = BinaryFormat::getCodePointAndForwardPointer(root, &pos); + int baseChar = toBaseLowerCase(codePoint); + const int wChar = toBaseLowerCase(inWord[startInputIndex]); if (baseChar != wChar) { *outPos = hasMultipleChars ? BinaryFormat::skipOtherCharacters(root, pos) : pos; @@ -759,8 +748,8 @@ static inline bool testCharGroupForContinuedLikeness(const uint8_t flags, // It will compare the frequency to the max frequency, and if greater, will // copy the word into the output buffer. In output value maxFreq, it will // write the new maximum frequency if it changed. -static inline void onTerminalWordLike(const int freq, int32_t *newWord, const int length, - short unsigned int *outWord, int *maxFreq) { +static inline void onTerminalWordLike(const int freq, int *newWord, const int length, int *outWord, + int *maxFreq) { if (freq > *maxFreq) { for (int q = 0; q < length; ++q) { outWord[q] = newWord[q]; @@ -772,9 +761,9 @@ static inline void onTerminalWordLike(const int freq, int32_t *newWord, const in // Will find the highest frequency of the words like the one passed as an argument, // that is, everything that only differs by case/accents. -int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t *const inWord, - const int inputSize, short unsigned int *outWord) const { - int32_t newWord[MAX_WORD_LENGTH_INTERNAL]; +int UnigramDictionary::getMostFrequentWordLikeInner(const int *const inWord, const int inputSize, + int *outWord) const { + int newWord[MAX_WORD_LENGTH_INTERNAL]; int depth = 0; int maxFreq = -1; const uint8_t *const root = DICT_ROOT; @@ -834,7 +823,7 @@ int UnigramDictionary::getMostFrequentWordLikeInner(const uint16_t *const inWord return maxFreq; } -int UnigramDictionary::getFrequency(const int32_t *const inWord, const int length) const { +int UnigramDictionary::getFrequency(const int *const inWord, const int length) const { const uint8_t *const root = DICT_ROOT; int pos = BinaryFormat::getTerminalPosition(root, inWord, length, false /* forceLowerCaseSearch */); @@ -859,8 +848,7 @@ int UnigramDictionary::getFrequency(const int32_t *const inWord, const int lengt } // TODO: remove this function. -int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offset, - int length) const { +int UnigramDictionary::getBigramPosition(int pos, int *word, int offset, int length) const { return -1; } @@ -878,7 +866,7 @@ int UnigramDictionary::getBigramPosition(int pos, unsigned short *word, int offs // there aren't any more nodes at this level, it merely returns the address of the first byte after // the current node in nextSiblingPosition. Thus, the caller must keep count of the nodes at any // given level, as output into newCount when traversing this level's parent. -inline bool UnigramDictionary::processCurrentNode(const int initialPos, +bool UnigramDictionary::processCurrentNode(const int initialPos, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, Correction *correction, int *newCount, int *newChildrenPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool, const int currentWordIndex) const { @@ -906,7 +894,7 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, // else if FLAG_IS_TERMINAL: the frequency // else if MASK_GROUP_ADDRESS_TYPE is not NONE: the children address // Note that you can't have a node that both is not a terminal and has no children. - int32_t c = BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos); + int c = BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos); assert(NOT_A_CODE_POINT != c); // We are going to loop through each character and make it look like it's a different @@ -920,7 +908,7 @@ inline bool UnigramDictionary::processCurrentNode(const int initialPos, // We prefetch the next char. If 'c' is the last char of this node, we will have // NOT_A_CODE_POINT in the next char. From this we can decide whether this virtual node // should behave as a terminal or not and whether we have children. - const int32_t nextc = hasMultipleChars + const int nextc = hasMultipleChars ? BinaryFormat::getCodePointAndForwardPointer(DICT_ROOT, &pos) : NOT_A_CODE_POINT; const bool isLastChar = (NOT_A_CODE_POINT == nextc); // If there are more chars in this nodes, then this virtual node is not a terminal. diff --git a/native/jni/src/unigram_dictionary.h b/native/jni/src/unigram_dictionary.h index 57129bb07..3162e46e5 100644 --- a/native/jni/src/unigram_dictionary.h +++ b/native/jni/src/unigram_dictionary.h @@ -39,14 +39,14 @@ class UnigramDictionary { static const int FLAG_MULTIPLE_SUGGEST_ABORT = 0; static const int FLAG_MULTIPLE_SUGGEST_SKIP = 1; static const int FLAG_MULTIPLE_SUGGEST_CONTINUE = 2; - UnigramDictionary(const uint8_t *const streamStart, int typedLetterMultipler, - int fullWordMultiplier, int maxWordLength, int maxWords, const unsigned int flags); - int getFrequency(const int32_t *const inWord, const int length) const; - int getBigramPosition(int pos, unsigned short *word, int offset, int length) const; + UnigramDictionary(const uint8_t *const streamStart, int maxWordLength, int maxWords, + const unsigned int flags); + int getFrequency(const int *const inWord, const int length) const; + int getBigramPosition(int pos, int *word, int offset, int length) const; int getSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const int codesSize, const std::map<int, int> *bigramMap, const uint8_t *bigramFilter, - const bool useFullEditDistance, unsigned short *outWords, int *frequencies, + const bool useFullEditDistance, int *outWords, int *frequencies, int *outputTypes) const; virtual ~UnigramDictionary(); @@ -93,9 +93,9 @@ class UnigramDictionary { int *newChildPosition, int *nextSiblingPosition, WordsPriorityQueuePool *queuePool, const int currentWordIndex) const; int getMostFrequentWordLike(const int startInputIndex, const int inputSize, - Correction *correction, unsigned short *word) const; - int getMostFrequentWordLikeInner(const uint16_t *const inWord, const int inputSize, - short unsigned int *outWord) const; + Correction *correction, int *word) const; + int getMostFrequentWordLikeInner(const int *const inWord, const int inputSize, + int *outWord) const; int getSubStringSuggestion( ProximityInfo *proximityInfo, const int *xcoordinates, const int *ycoordinates, const int *codes, const bool useFullEditDistance, Correction *correction, @@ -103,22 +103,18 @@ class UnigramDictionary { const bool hasAutoCorrectionCandidate, const int currentWordIndex, const int inputWordStartPos, const int inputWordLength, const int outputWordStartPos, const bool isSpaceProximity, int *freqArray, - int *wordLengthArray, unsigned short *outputWord, int *outputWordLength) const; - void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, - const int *xcoordinates, const int *ycoordinates, const int *codes, - const bool useFullEditDistance, const int inputSize, - Correction *correction, WordsPriorityQueuePool *queuePool, + int *wordLengthArray, int *outputWord, int *outputWordLength) const; + void getMultiWordsSuggestionRec(ProximityInfo *proximityInfo, const int *xcoordinates, + const int *ycoordinates, const int *codes, const bool useFullEditDistance, + const int inputSize, Correction *correction, WordsPriorityQueuePool *queuePool, const bool hasAutoCorrectionCandidate, const int startPos, const int startWordIndex, const int outputWordLength, int *freqArray, int *wordLengthArray, - unsigned short *outputWord) const; + int *outputWord) const; const uint8_t *const DICT_ROOT; const int MAX_WORD_LENGTH; const int MAX_WORDS; - const int TYPED_LETTER_MULTIPLIER; - const int FULL_WORD_MULTIPLIER; const int ROOT_POS; - const unsigned int BYTES_IN_ONE_CHAR; const int MAX_DIGRAPH_SEARCH_DEPTH; const int FLAGS; diff --git a/native/jni/src/words_priority_queue.cpp b/native/jni/src/words_priority_queue.cpp new file mode 100644 index 000000000..7e18d0f87 --- /dev/null +++ b/native/jni/src/words_priority_queue.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "words_priority_queue.h" + +namespace latinime { + +int WordsPriorityQueue::outputSuggestions(const int *before, const int beforeLength, + int *frequencies, int *outputCodePoints, int* outputTypes) { + mHighestSuggestedWord = 0; + const int size = min(MAX_WORDS, static_cast<int>(mSuggestions.size())); + SuggestedWord *swBuffer[size]; + int index = size - 1; + while (!mSuggestions.empty() && index >= 0) { + SuggestedWord *sw = mSuggestions.top(); + if (DEBUG_WORDS_PRIORITY_QUEUE) { + AKLOGI("dump word. %d", sw->mScore); + DUMP_WORD(sw->mWord, sw->mWordLength); + } + swBuffer[index] = sw; + mSuggestions.pop(); + --index; + } + if (size >= 2) { + SuggestedWord *nsMaxSw = 0; + int maxIndex = 0; + float maxNs = 0; + for (int i = 0; i < size; ++i) { + SuggestedWord *tempSw = swBuffer[i]; + if (!tempSw) { + continue; + } + const float tempNs = getNormalizedScore(tempSw, before, beforeLength, 0, 0, 0); + if (tempNs >= maxNs) { + maxNs = tempNs; + maxIndex = i; + nsMaxSw = tempSw; + } + } + if (maxIndex > 0 && nsMaxSw) { + memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(swBuffer[0])); + swBuffer[0] = nsMaxSw; + } + } + for (int i = 0; i < size; ++i) { + SuggestedWord *sw = swBuffer[i]; + if (!sw) { + AKLOGE("SuggestedWord is null %d", i); + continue; + } + const int wordLength = sw->mWordLength; + int *targetAddress = outputCodePoints + i * MAX_WORD_LENGTH; + frequencies[i] = sw->mScore; + outputTypes[i] = sw->mType; + memcpy(targetAddress, sw->mWord, wordLength * sizeof(targetAddress[0])); + if (wordLength < MAX_WORD_LENGTH) { + targetAddress[wordLength] = 0; + } + sw->mUsed = false; + } + return size; +} +} // namespace latinime diff --git a/native/jni/src/words_priority_queue.h b/native/jni/src/words_priority_queue.h index 19efa5da3..e613d2f8a 100644 --- a/native/jni/src/words_priority_queue.h +++ b/native/jni/src/words_priority_queue.h @@ -30,15 +30,15 @@ class WordsPriorityQueue { class SuggestedWord { public: int mScore; - unsigned short mWord[MAX_WORD_LENGTH_INTERNAL]; + int mWord[MAX_WORD_LENGTH_INTERNAL]; int mWordLength; bool mUsed; int mType; - void setParams(int score, unsigned short *word, int wordLength, int type) { + void setParams(int score, int *word, int wordLength, int type) { mScore = score; mWordLength = wordLength; - memcpy(mWord, word, sizeof(unsigned short) * wordLength); + memcpy(mWord, word, sizeof(mWord[0]) * wordLength); mUsed = true; mType = type; } @@ -53,13 +53,14 @@ class WordsPriorityQueue { } } - virtual ~WordsPriorityQueue() { + // Non virtual inline destructor -- never inherit this class + AK_FORCE_INLINE ~WordsPriorityQueue() { delete[] mSuggestedWords; } - void push(int score, unsigned short *word, int wordLength, int type) { + void push(int score, int *word, int wordLength, int type) { SuggestedWord *sw = 0; - if (mSuggestions.size() >= MAX_WORDS) { + if (size() >= MAX_WORDS) { sw = mSuggestions.top(); const int minScore = sw->mScore; if (minScore >= score) { @@ -94,68 +95,11 @@ class WordsPriorityQueue { return sw; } - int outputSuggestions(const unsigned short *before, const int beforeLength, - int *frequencies, unsigned short *outputChars, int* outputTypes) { - mHighestSuggestedWord = 0; - const unsigned int size = min( - MAX_WORDS, static_cast<unsigned int>(mSuggestions.size())); - SuggestedWord *swBuffer[size]; - int index = size - 1; - while (!mSuggestions.empty() && index >= 0) { - SuggestedWord *sw = mSuggestions.top(); - if (DEBUG_WORDS_PRIORITY_QUEUE) { - AKLOGI("dump word. %d", sw->mScore); - DUMP_WORD(sw->mWord, sw->mWordLength); - } - swBuffer[index] = sw; - mSuggestions.pop(); - --index; - } - if (size >= 2) { - SuggestedWord *nsMaxSw = 0; - unsigned int maxIndex = 0; - float maxNs = 0; - for (unsigned int i = 0; i < size; ++i) { - SuggestedWord *tempSw = swBuffer[i]; - if (!tempSw) { - continue; - } - const float tempNs = getNormalizedScore(tempSw, before, beforeLength, 0, 0, 0); - if (tempNs >= maxNs) { - maxNs = tempNs; - maxIndex = i; - nsMaxSw = tempSw; - } - } - if (maxIndex > 0 && nsMaxSw) { - memmove(&swBuffer[1], &swBuffer[0], maxIndex * sizeof(SuggestedWord *)); - swBuffer[0] = nsMaxSw; - } - } - for (unsigned int i = 0; i < size; ++i) { - SuggestedWord *sw = swBuffer[i]; - if (!sw) { - AKLOGE("SuggestedWord is null %d", i); - continue; - } - const unsigned int wordLength = sw->mWordLength; - unsigned short *targetAddress = outputChars + i * MAX_WORD_LENGTH; - frequencies[i] = sw->mScore; - outputTypes[i] = sw->mType; - memcpy(targetAddress, sw->mWord, wordLength * sizeof(unsigned short)); - if (wordLength < MAX_WORD_LENGTH) { - targetAddress[wordLength] = 0; - } - sw->mUsed = false; - } - return size; - } - int size() const { - return mSuggestions.size(); + return static_cast<int>(mSuggestions.size()); } - void clear() { + AK_FORCE_INLINE void clear() { mHighestSuggestedWord = 0; while (!mSuggestions.empty()) { SuggestedWord *sw = mSuggestions.top(); @@ -168,22 +112,25 @@ class WordsPriorityQueue { } } - void dumpTopWord() { + AK_FORCE_INLINE void dumpTopWord() { if (size() <= 0) { return; } DUMP_WORD(mHighestSuggestedWord->mWord, mHighestSuggestedWord->mWordLength); } - float getHighestNormalizedScore(const unsigned short *before, const int beforeLength, - unsigned short **outWord, int *outScore, int *outLength) { + AK_FORCE_INLINE float getHighestNormalizedScore(const int *before, const int beforeLength, + int **outWord, int *outScore, int *outLength) { if (!mHighestSuggestedWord) { return 0.0; } - return getNormalizedScore( - mHighestSuggestedWord, before, beforeLength, outWord, outScore, outLength); + return getNormalizedScore(mHighestSuggestedWord, before, beforeLength, outWord, outScore, + outLength); } + int outputSuggestions(const int *before, const int beforeLength, int *frequencies, + int *outputCodePoints, int* outputTypes); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(WordsPriorityQueue); struct wordComparator { @@ -192,9 +139,8 @@ class WordsPriorityQueue { } }; - SuggestedWord *getFreeSuggestedWord(int score, unsigned short *word, - int wordLength, int type) { - for (unsigned int i = 0; i < MAX_WORD_LENGTH; ++i) { + SuggestedWord *getFreeSuggestedWord(int score, int *word, int wordLength, int type) { + for (int i = 0; i < MAX_WORD_LENGTH; ++i) { if (!mSuggestedWords[i].mUsed) { mSuggestedWords[i].setParams(score, word, wordLength, type); return &mSuggestedWords[i]; @@ -203,10 +149,10 @@ class WordsPriorityQueue { return 0; } - static float getNormalizedScore(SuggestedWord *sw, const unsigned short *before, - const int beforeLength, unsigned short **outWord, int *outScore, int *outLength) { + static float getNormalizedScore(SuggestedWord *sw, const int *before, const int beforeLength, + int **outWord, int *outScore, int *outLength) { const int score = sw->mScore; - unsigned short *word = sw->mWord; + int *word = sw->mWord; const int wordLength = sw->mWordLength; if (outScore) { *outScore = score; @@ -217,15 +163,15 @@ class WordsPriorityQueue { if (outLength) { *outLength = wordLength; } - return Correction::RankingAlgorithm::calcNormalizedScore( - before, beforeLength, word, wordLength, score); + return Correction::RankingAlgorithm::calcNormalizedScore(before, beforeLength, word, + wordLength, score); } typedef std::priority_queue<SuggestedWord *, std::vector<SuggestedWord *>, wordComparator> Suggestions; Suggestions mSuggestions; - const unsigned int MAX_WORDS; - const unsigned int MAX_WORD_LENGTH; + const int MAX_WORDS; + const int MAX_WORD_LENGTH; SuggestedWord *mSuggestedWords; SuggestedWord *mHighestSuggestedWord; }; diff --git a/native/jni/src/words_priority_queue_pool.h b/native/jni/src/words_priority_queue_pool.h index 2d52903e0..c14afa07b 100644 --- a/native/jni/src/words_priority_queue_pool.h +++ b/native/jni/src/words_priority_queue_pool.h @@ -36,7 +36,8 @@ class WordsPriorityQueuePool { } } - virtual ~WordsPriorityQueuePool() { + // Non virtual inline destructor -- never inherit this class + ~WordsPriorityQueuePool() { // Note: these explicit calls to the destructor match the calls to placement new() above. if (mMasterQueue) mMasterQueue->~WordsPriorityQueue(); for (int i = 0; i < MULTIPLE_WORDS_SUGGESTION_MAX_WORDS * SUB_QUEUE_MAX_COUNT; ++i) { @@ -68,7 +69,7 @@ class WordsPriorityQueuePool { } } - inline void clearSubQueue(const int wordIndex) { + AK_FORCE_INLINE void clearSubQueue(const int wordIndex) { for (int i = 0; i < SUB_QUEUE_MAX_COUNT; ++i) { WordsPriorityQueue *queue = getSubQueue(wordIndex, i); if (queue) { diff --git a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java index bc5043911..015c2711c 100644 --- a/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java +++ b/tests/src/com/android/inputmethod/keyboard/SpacebarTextTests.java @@ -23,7 +23,7 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.AdditionalSubtype; import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.ImfUtils; +import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SubtypeLocale; @@ -34,12 +34,15 @@ public class SpacebarTextTests extends AndroidTestCase { // Locale to subtypes list. private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList(); + private RichInputMethodManager mRichImm; private Resources mRes; @Override protected void setUp() throws Exception { super.setUp(); final Context context = getContext(); + RichInputMethodManager.init(context); + mRichImm = RichInputMethodManager.getInstance(); mRes = context.getResources(); SubtypeLocale.init(context); } @@ -103,19 +106,18 @@ public class SpacebarTextTests extends AndroidTestCase { // zz azerty T AZERTY AZERTY public void testPredefinedSubtypes() { - final Context context = getContext(); - final InputMethodSubtype EN_US = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.US.toString(), "qwerty"); - final InputMethodSubtype EN_GB = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.UK.toString(), "qwerty"); - final InputMethodSubtype FR = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.FRENCH.toString(), "azerty"); - final InputMethodSubtype FR_CA = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.CANADA_FRENCH.toString(), "qwerty"); - final InputMethodSubtype DE = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.GERMAN.toString(), "qwertz"); - final InputMethodSubtype ZZ = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, SubtypeLocale.NO_LANGUAGE, "qwerty"); + final InputMethodSubtype EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.US.toString(), "qwerty"); + final InputMethodSubtype EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.UK.toString(), "qwerty"); + final InputMethodSubtype FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.FRENCH.toString(), "azerty"); + final InputMethodSubtype FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.CANADA_FRENCH.toString(), "qwerty"); + final InputMethodSubtype DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.GERMAN.toString(), "qwertz"); + final InputMethodSubtype ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, "qwerty"); assertEquals("en_US", "English (US)", MainKeyboardView.getFullDisplayName(EN_US, mRes)); diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java index 1ab577557..5bba961bd 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeySpecParserTests.java @@ -16,13 +16,13 @@ package com.android.inputmethod.keyboard.internal; -import static com.android.inputmethod.keyboard.Keyboard.CODE_OUTPUT_TEXT; -import static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED; import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; +import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT; +import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.test.AndroidTestCase; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; import java.util.Arrays; import java.util.Locale; @@ -69,8 +69,8 @@ public class KeySpecParserTests extends AndroidTestCase { KeyboardIconsSet.getIconName(expectedIcon), KeyboardIconsSet.getIconName(spec.mIconId)); assertEquals(message + " [code]", - Keyboard.printableCode(expectedCode), - Keyboard.printableCode(spec.mCode)); + Constants.printableCode(expectedCode), + Constants.printableCode(spec.mCode)); } private void assertParserError(String message, String moreKeySpec, String expectedLabel, diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java index 2770ed54a..c75f8269a 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java @@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.test.AndroidTestCase; public class KeyboardStateTestsBase extends AndroidTestCase - implements MockKeyboardSwitcher.Constants { + implements MockKeyboardSwitcher.MockConstants { protected MockKeyboardSwitcher mSwitcher; private String mLayoutSwitchBackSymbols = ""; diff --git a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java index dd4caadf1..0213744fb 100644 --- a/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java +++ b/tests/src/com/android/inputmethod/keyboard/internal/MockKeyboardSwitcher.java @@ -18,25 +18,24 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; -import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.latin.Constants; public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { - public interface Constants { + public interface MockConstants { // Argument for {@link KeyboardState#onPressKey} and {@link KeyboardState#onReleaseKey}. public static final boolean NOT_SLIDING = false; public static final boolean SLIDING = true; // Argument for {@link KeyboardState#onCodeInput}. public static final boolean SINGLE = true; public static final boolean MULTI = false; - public static final int CAP_MODE_OFF = - com.android.inputmethod.latin.Constants.TextUtils.CAP_MODE_OFF; + public static final int CAP_MODE_OFF = Constants.TextUtils.CAP_MODE_OFF; public static final int CAP_MODE_WORDS = TextUtils.CAP_MODE_WORDS; public static final int CAP_MODE_CHARACTERS = TextUtils.CAP_MODE_CHARACTERS; - public static final int CODE_SHIFT = Keyboard.CODE_SHIFT; - public static final int CODE_SYMBOL = Keyboard.CODE_SWITCH_ALPHA_SYMBOL; - public static final int CODE_SPACE = Keyboard.CODE_SPACE; - public static final int CODE_AUTO_CAPS_TRIGGER = Keyboard.CODE_SPACE; + public static final int CODE_SHIFT = Constants.CODE_SHIFT; + public static final int CODE_SYMBOL = Constants.CODE_SWITCH_ALPHA_SYMBOL; + public static final int CODE_SPACE = Constants.CODE_SPACE; + public static final int CODE_AUTO_CAPS_TRIGGER = Constants.CODE_SPACE; public static final int ALPHABET_UNSHIFTED = 0; public static final int ALPHABET_MANUAL_SHIFTED = 1; @@ -47,11 +46,11 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { public static final int SYMBOLS_SHIFTED = 6; } - private int mLayout = Constants.ALPHABET_UNSHIFTED; + private int mLayout = MockConstants.ALPHABET_UNSHIFTED; - private int mAutoCapsMode = Constants.CAP_MODE_OFF; + private int mAutoCapsMode = MockConstants.CAP_MODE_OFF; // Following InputConnection's behavior. Simulating InputType.TYPE_TEXT_FLAG_CAP_WORDS. - private int mAutoCapsState = Constants.CAP_MODE_OFF; + private int mAutoCapsState = MockConstants.CAP_MODE_OFF; private boolean mIsInDoubleTapTimeout; private int mLongPressTimeoutCode; @@ -64,13 +63,13 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { public static String getLayoutName(int layoutId) { switch (layoutId) { - case Constants.ALPHABET_UNSHIFTED: return "ALPHABET_UNSHIFTED"; - case Constants.ALPHABET_MANUAL_SHIFTED: return "ALPHABET_MANUAL_SHIFTED"; - case Constants.ALPHABET_AUTOMATIC_SHIFTED: return "ALPHABET_AUTOMATIC_SHIFTED"; - case Constants.ALPHABET_SHIFT_LOCKED: return "ALPHABET_SHIFT_LOCKED"; - case Constants.ALPHABET_SHIFT_LOCK_SHIFTED: return "ALPHABET_SHIFT_LOCK_SHIFTED"; - case Constants.SYMBOLS_UNSHIFTED: return "SYMBOLS_UNSHIFTED"; - case Constants.SYMBOLS_SHIFTED: return "SYMBOLS_SHIFTED"; + case MockConstants.ALPHABET_UNSHIFTED: return "ALPHABET_UNSHIFTED"; + case MockConstants.ALPHABET_MANUAL_SHIFTED: return "ALPHABET_MANUAL_SHIFTED"; + case MockConstants.ALPHABET_AUTOMATIC_SHIFTED: return "ALPHABET_AUTOMATIC_SHIFTED"; + case MockConstants.ALPHABET_SHIFT_LOCKED: return "ALPHABET_SHIFT_LOCKED"; + case MockConstants.ALPHABET_SHIFT_LOCK_SHIFTED: return "ALPHABET_SHIFT_LOCK_SHIFTED"; + case MockConstants.SYMBOLS_UNSHIFTED: return "SYMBOLS_UNSHIFTED"; + case MockConstants.SYMBOLS_SHIFTED: return "SYMBOLS_SHIFTED"; default: return "UNKNOWN<" + layoutId + ">"; } } @@ -86,37 +85,37 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { @Override public void setAlphabetKeyboard() { - mLayout = Constants.ALPHABET_UNSHIFTED; + mLayout = MockConstants.ALPHABET_UNSHIFTED; } @Override public void setAlphabetManualShiftedKeyboard() { - mLayout = Constants.ALPHABET_MANUAL_SHIFTED; + mLayout = MockConstants.ALPHABET_MANUAL_SHIFTED; } @Override public void setAlphabetAutomaticShiftedKeyboard() { - mLayout = Constants.ALPHABET_AUTOMATIC_SHIFTED; + mLayout = MockConstants.ALPHABET_AUTOMATIC_SHIFTED; } @Override public void setAlphabetShiftLockedKeyboard() { - mLayout = Constants.ALPHABET_SHIFT_LOCKED; + mLayout = MockConstants.ALPHABET_SHIFT_LOCKED; } @Override public void setAlphabetShiftLockShiftedKeyboard() { - mLayout = Constants.ALPHABET_SHIFT_LOCK_SHIFTED; + mLayout = MockConstants.ALPHABET_SHIFT_LOCK_SHIFTED; } @Override public void setSymbolsKeyboard() { - mLayout = Constants.SYMBOLS_UNSHIFTED; + mLayout = MockConstants.SYMBOLS_UNSHIFTED; } @Override public void setSymbolsShiftedKeyboard() { - mLayout = Constants.SYMBOLS_SHIFTED; + mLayout = MockConstants.SYMBOLS_SHIFTED; } @Override @@ -186,10 +185,10 @@ public class MockKeyboardSwitcher implements KeyboardState.SwitchActions { } public void onCodeInput(int code, boolean isSinglePointer) { - if (mAutoCapsMode == Constants.CAP_MODE_WORDS) { - if (Keyboard.isLetterCode(code)) { - mAutoCapsState = (code == Constants.CODE_AUTO_CAPS_TRIGGER) - ? mAutoCapsMode : Constants.CAP_MODE_OFF; + if (mAutoCapsMode == MockConstants.CAP_MODE_WORDS) { + if (Constants.isLetterCode(code)) { + mAutoCapsState = (code == MockConstants.CODE_AUTO_CAPS_TRIGGER) + ? mAutoCapsMode : MockConstants.CAP_MODE_OFF; } } else { mAutoCapsState = mAutoCapsMode; diff --git a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java index a9947c1bd..02047cfc4 100644 --- a/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java +++ b/tests/src/com/android/inputmethod/latin/BlueUnderlineTests.java @@ -16,8 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.keyboard.Keyboard; - import android.text.style.SuggestionSpan; import android.text.style.UnderlineSpan; @@ -67,13 +65,13 @@ public class BlueUnderlineTests extends InputTestsBase { type(STRING_TO_TYPE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); - type(Keyboard.CODE_SPACE); + type(Constants.CODE_SPACE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); final SpanGetter suggestionSpan = new SpanGetter(mTextView.getText(), SuggestionSpan.class); @@ -88,17 +86,19 @@ public class BlueUnderlineTests extends InputTestsBase { public void testBlueUnderlineDisappearsWhenCursorMoved() { final String STRING_TO_TYPE = "tgis"; + final int typedLength = STRING_TO_TYPE.length(); final int NEW_CURSOR_POSITION = 0; type(STRING_TO_TYPE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); // Simulate the onUpdateSelection() event - mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); runMessages(); // Here the blue underline has been set. testBlueUnderline() is testing for this already, // so let's not test it here again. // Now simulate the user moving the cursor. mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); - mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); + mLatinIME.onUpdateSelection(typedLength, typedLength, + NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); final SpanGetter span = new SpanGetter(mTextView.getText(), SuggestionSpan.class); diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 38e57aaed..38f5305de 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -16,8 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.keyboard.Keyboard; - public class InputLogicTests extends InputTestsBase { public void testTypeWord() { @@ -32,7 +30,7 @@ public class InputLogicTests extends InputTestsBase { type(WORD_TO_TYPE); pickSuggestionManually(0, WORD_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("press suggestion then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -48,7 +46,7 @@ public class InputLogicTests extends InputTestsBase { mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick typed word over auto-correction then backspace", WORD_TO_PICK, mTextView.getText().toString()); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -63,7 +61,7 @@ public class InputLogicTests extends InputTestsBase { mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick typed word over auto-correction then backspace", WORD_TO_TYPE, mTextView.getText().toString()); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("pick typed word over auto-correction then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -79,13 +77,14 @@ public class InputLogicTests extends InputTestsBase { mLatinIME.onUpdateSelection(0, 0, WORD_TO_TYPE.length(), WORD_TO_TYPE.length(), -1, -1); assertEquals("pick different suggestion then backspace", WORD_TO_PICK, mTextView.getText().toString()); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("pick different suggestion then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } public void testDeleteSelection() { final String STRING_TO_TYPE = "some text delete me some text"; + final int typedLength = STRING_TO_TYPE.length(); final int SELECTION_START = 10; final int SELECTION_END = 19; final String EXPECTED_RESULT = "some text some text"; @@ -94,11 +93,12 @@ public class InputLogicTests extends InputTestsBase { // Send once to simulate the cursor actually responding to the move caused by typing. // This is necessary because LatinIME is bookkeeping to avoid confusing a real cursor // move with a move triggered by LatinIME inputting stuff. - mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); mInputConnection.setSelection(SELECTION_START, SELECTION_END); // And now we simulate the user actually selecting some text. - mLatinIME.onUpdateSelection(0, 0, SELECTION_START, SELECTION_END, -1, -1); - type(Keyboard.CODE_DELETE); + mLatinIME.onUpdateSelection(typedLength, typedLength, + SELECTION_START, SELECTION_END, -1, -1); + type(Constants.CODE_DELETE); assertEquals("delete selection", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -121,7 +121,7 @@ public class InputLogicTests extends InputTestsBase { final String EXPECTED_RESULT = "tgis."; type(STRING_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("auto-correct with period then revert", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -131,7 +131,7 @@ public class InputLogicTests extends InputTestsBase { final String EXPECTED_RESULT = "tgis "; type(STRING_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("auto-correct with space then revert", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -141,7 +141,7 @@ public class InputLogicTests extends InputTestsBase { final String EXPECTED_RESULT = "this"; type(STRING_TO_TYPE); mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("auto-correct with space does not revert", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -157,32 +157,36 @@ public class InputLogicTests extends InputTestsBase { final String STRING_TO_TYPE = "this "; final String EXPECTED_RESULT = "this "; type(STRING_TO_TYPE); - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); assertEquals("double space make a period", EXPECTED_RESULT, mTextView.getText().toString()); } public void testBackspaceAtStartAfterAutocorrect() { final String STRING_TO_TYPE = "tgis "; + final int typedLength = STRING_TO_TYPE.length(); final String EXPECTED_RESULT = "this "; final int NEW_CURSOR_POSITION = 0; type(STRING_TO_TYPE); - mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); - mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); - type(Keyboard.CODE_DELETE); + mLatinIME.onUpdateSelection(typedLength, typedLength, + NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); + type(Constants.CODE_DELETE); assertEquals("auto correct then move cursor to start of line then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } public void testAutoCorrectThenMoveCursorThenBackspace() { final String STRING_TO_TYPE = "and tgis "; + final int typedLength = STRING_TO_TYPE.length(); final String EXPECTED_RESULT = "andthis "; final int NEW_CURSOR_POSITION = STRING_TO_TYPE.indexOf('t'); type(STRING_TO_TYPE); - mLatinIME.onUpdateSelection(0, 0, STRING_TO_TYPE.length(), STRING_TO_TYPE.length(), -1, -1); + mLatinIME.onUpdateSelection(0, 0, typedLength, typedLength, -1, -1); mInputConnection.setSelection(NEW_CURSOR_POSITION, NEW_CURSOR_POSITION); - mLatinIME.onUpdateSelection(0, 0, NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); - type(Keyboard.CODE_DELETE); + mLatinIME.onUpdateSelection(typedLength, typedLength, + NEW_CURSOR_POSITION, NEW_CURSOR_POSITION, -1, -1); + type(Constants.CODE_DELETE); assertEquals("auto correct then move cursor then backspace", EXPECTED_RESULT, mTextView.getText().toString()); } @@ -258,7 +262,7 @@ public class InputLogicTests extends InputTestsBase { final String WORD_TO_TYPE = "this"; type(WORD_TO_TYPE); for (int i = 0; i < WORD_TO_TYPE.length(); ++i) { - type(Keyboard.CODE_DELETE); + type(Constants.CODE_DELETE); } assertEquals("delete whole composing word", "", mTextView.getText().toString()); } diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java index 78143ac5b..42823f538 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java @@ -16,7 +16,10 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.suggestions.SuggestionStripView; + public class InputLogicTestsNonEnglish extends InputTestsBase { + final String NEXT_WORD_PREDICTION_OPTION = "next_word_prediction"; public void testAutoCorrectForFrench() { final String STRING_TO_TYPE = "irq "; @@ -43,16 +46,40 @@ public class InputLogicTestsNonEnglish extends InputTestsBase { final String WORD_TO_TYPE = "test "; final String PUNCTUATION_FROM_STRIP = "!"; final String EXPECTED_RESULT = "test !!"; + final boolean defaultNextWordPredictionOption = + mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_prediction); + final boolean previousNextWordPredictionOption = + setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, false, + defaultNextWordPredictionOption); + try { + changeLanguage("fr"); + type(WORD_TO_TYPE); + sleep(DELAY_TO_WAIT_FOR_UNDERLINE); + runMessages(); + assertTrue("type word then type space should display punctuation strip", + mLatinIME.isShowingPunctuationList()); + pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); + pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); + assertEquals("type word then type space then punctuation from strip twice for French", + EXPECTED_RESULT, mTextView.getText().toString()); + } finally { + setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, previousNextWordPredictionOption, + defaultNextWordPredictionOption); + } + } + + public void testWordThenSpaceDisplaysPredictions() { + final String WORD_TO_TYPE = "beaujolais "; + final String EXPECTED_RESULT = "nouveau"; changeLanguage("fr"); type(WORD_TO_TYPE); sleep(DELAY_TO_WAIT_FOR_UNDERLINE); runMessages(); - assertTrue("type word then type space should display punctuation strip", - mLatinIME.isShowingPunctuationList()); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); - pickSuggestionManually(0, PUNCTUATION_FROM_STRIP); - assertEquals("type word then type space then punctuation from strip twice for French", - EXPECTED_RESULT, mTextView.getText().toString()); + final SuggestionStripView suggestionStripView = + (SuggestionStripView)mInputView.findViewById(R.id.suggestion_strip_view); + final SuggestedWords suggestedWords = suggestionStripView.getSuggestions(); + assertEquals("type word then type space yields predictions for French", + EXPECTED_RESULT, suggestedWords.getWord(0)); } public void testAutoCorrectForGerman() { diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java index fe58cb84e..70330509f 100644 --- a/tests/src/com/android/inputmethod/latin/InputTestsBase.java +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -53,6 +53,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIME> { protected LatinIME mLatinIME; protected Keyboard mKeyboard; protected MyTextView mTextView; + protected View mInputView; protected InputConnection mInputConnection; private final HashMap<String, InputMethodSubtype> mSubtypeMap = new HashMap<String, InputMethodSubtype>(); @@ -150,9 +151,9 @@ public class InputTestsBase extends ServiceTestCase<LatinIME> { final LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); final ViewGroup vg = new FrameLayout(getContext()); - final View inputView = inflater.inflate(R.layout.input_view, vg); + mInputView = inflater.inflate(R.layout.input_view, vg); mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); - mLatinIME.setInputView(inputView); + mLatinIME.setInputView(mInputView); mLatinIME.onBindInput(); mLatinIME.onCreateInputView(); mLatinIME.onStartInputView(ei, false); @@ -211,7 +212,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIME> { // any subsequent post in this queue. However the queue itself is still fully functional! // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, // coming back to this method to run the messages. - MessageQueue queue = looper.getQueue(); + MessageQueue queue = Looper.myQueue(); try { // However there is no way of doing it externally, and mQuiting is private. // So... get out the big guns. @@ -280,7 +281,7 @@ public class InputTestsBase extends ServiceTestCase<LatinIME> { waitForDictionaryToBeLoaded(); } - protected void pickSuggestionManually(final int index, final CharSequence suggestion) { + protected void pickSuggestionManually(final int index, final String suggestion) { mLatinIME.pickSuggestionManually(index, suggestion); } diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java index 52a3745fa..4e81de668 100644 --- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java +++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java @@ -30,12 +30,15 @@ public class SubtypeLocaleTests extends AndroidTestCase { // Locale to subtypes list. private final ArrayList<InputMethodSubtype> mSubtypesList = CollectionUtils.newArrayList(); + private RichInputMethodManager mRichImm; private Resources mRes; @Override protected void setUp() throws Exception { super.setUp(); final Context context = getContext(); + RichInputMethodManager.init(context); + mRichImm = RichInputMethodManager.getInstance(); mRes = context.getResources(); SubtypeLocale.init(context); } @@ -56,36 +59,39 @@ public class SubtypeLocaleTests extends AndroidTestCase { // InputMethodSubtype's display name in its locale. // isAdditionalSubtype (T=true, F=false) - // locale layout | display name - // ------ ------ - ---------------------- - // en_US qwerty F English (US) exception - // en_GB qwerty F English (UK) exception - // fr azerty F Français - // fr_CA qwerty F Français (Canada) - // de qwertz F Deutsch - // zz qwerty F No language (QWERTY) in system locale - // fr qwertz T Français (QWERTZ) - // de qwerty T Deutsch (QWERTY) - // en_US azerty T English (US) (AZERTY) - // zz azerty T No language (AZERTY) in system locale + // locale layout | display name + // ------ ------- - ---------------------- + // en_US qwerty F English (US) exception + // en_GB qwerty F English (UK) exception + // es_US spanish F Español (EE.UU.) exception + // fr azerty F Français + // fr_CA qwerty F Français (Canada) + // de qwertz F Deutsch + // zz qwerty F No language (QWERTY) in system locale + // fr qwertz T Français (QWERTZ) + // de qwerty T Deutsch (QWERTY) + // en_US azerty T English (US) (AZERTY) + // zz azerty T No language (AZERTY) in system locale public void testPredefinedSubtypesInEnglish() { - final Context context = getContext(); - final InputMethodSubtype EN_US = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.US.toString(), "qwerty"); - final InputMethodSubtype EN_GB = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.UK.toString(), "qwerty"); - final InputMethodSubtype FR = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.FRENCH.toString(), "azerty"); - final InputMethodSubtype FR_CA = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.CANADA_FRENCH.toString(), "qwerty"); - final InputMethodSubtype DE = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.GERMAN.toString(), "qwertz"); - final InputMethodSubtype ZZ = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, SubtypeLocale.NO_LANGUAGE, "qwerty"); + final InputMethodSubtype EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.US.toString(), "qwerty"); + final InputMethodSubtype EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.UK.toString(), "qwerty"); + final InputMethodSubtype ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "es_US", "spanish"); + final InputMethodSubtype FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.FRENCH.toString(), "azerty"); + final InputMethodSubtype FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.CANADA_FRENCH.toString(), "qwerty"); + final InputMethodSubtype DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.GERMAN.toString(), "qwertz"); + final InputMethodSubtype ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, "qwerty"); assertEquals("en_US", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_US)); assertEquals("en_GB", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(EN_GB)); + assertEquals("es_US", "spanish", SubtypeLocale.getKeyboardLayoutSetName(ES_US)); assertEquals("fr ", "azerty", SubtypeLocale.getKeyboardLayoutSetName(FR)); assertEquals("fr_CA", "qwerty", SubtypeLocale.getKeyboardLayoutSetName(FR_CA)); assertEquals("de ", "qwertz", SubtypeLocale.getKeyboardLayoutSetName(DE)); @@ -98,6 +104,8 @@ public class SubtypeLocaleTests extends AndroidTestCase { SubtypeLocale.getSubtypeDisplayName(EN_US, res)); assertEquals("en_GB", "English (UK)", SubtypeLocale.getSubtypeDisplayName(EN_GB, res)); + assertEquals("es_US", "Español (EE.UU.)", + SubtypeLocale.getSubtypeDisplayName(ES_US, res)); assertEquals("fr ", "Français", SubtypeLocale.getSubtypeDisplayName(FR, res)); assertEquals("fr_CA", "Français (Canada)", @@ -140,19 +148,20 @@ public class SubtypeLocaleTests extends AndroidTestCase { } public void testPredefinedSubtypesInFrench() { - final Context context = getContext(); - final InputMethodSubtype EN_US = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.US.toString(), "qwerty"); - final InputMethodSubtype EN_GB = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.UK.toString(), "qwerty"); - final InputMethodSubtype FR = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.FRENCH.toString(), "azerty"); - final InputMethodSubtype FR_CA = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.CANADA_FRENCH.toString(), "qwerty"); - final InputMethodSubtype DE = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, Locale.GERMAN.toString(), "qwertz"); - final InputMethodSubtype ZZ = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - context, SubtypeLocale.NO_LANGUAGE, "qwerty"); + final InputMethodSubtype EN_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.US.toString(), "qwerty"); + final InputMethodSubtype EN_GB = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.UK.toString(), "qwerty"); + final InputMethodSubtype ES_US = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + "es_US", "spanish"); + final InputMethodSubtype FR = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.FRENCH.toString(), "azerty"); + final InputMethodSubtype FR_CA = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.CANADA_FRENCH.toString(), "qwerty"); + final InputMethodSubtype DE = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + Locale.GERMAN.toString(), "qwertz"); + final InputMethodSubtype ZZ = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, "qwerty"); final RunInLocale<Void> tests = new RunInLocale<Void>() { @Override @@ -161,6 +170,8 @@ public class SubtypeLocaleTests extends AndroidTestCase { SubtypeLocale.getSubtypeDisplayName(EN_US, res)); assertEquals("en_GB", "English (UK)", SubtypeLocale.getSubtypeDisplayName(EN_GB, res)); + assertEquals("es_US", "Español (EE.UU.)", + SubtypeLocale.getSubtypeDisplayName(ES_US, res)); assertEquals("fr ", "Français", SubtypeLocale.getSubtypeDisplayName(FR, res)); assertEquals("fr_CA", "Français (Canada)", diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java index 70f916c1a..66c9e1851 100644 --- a/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java +++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictIOUtilsTests.java @@ -188,7 +188,7 @@ public class UserHistoryDictIOUtilsTests extends AndroidTestCase File file = null; try { - file = File.createTempFile("testReadAndWrite", ".dict"); + file = File.createTempFile("testReadAndWrite", ".dict", getContext().getCacheDir()); } catch (IOException e) { Log.d(TAG, "IOException while creating a temporary file: " + e); } diff --git a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java index f2a17d206..7536f64ac 100644 --- a/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java +++ b/tests/src/com/android/inputmethod/latin/UserHistoryDictionaryTests.java @@ -23,6 +23,8 @@ import android.preference.PreferenceManager; import android.test.AndroidTestCase; import android.util.Log; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -76,34 +78,43 @@ public class UserHistoryDictionaryTests extends AndroidTestCase { } public void testRandomWords() { - Log.d(TAG, "This test can be used for profiling."); - Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true."); - final int numberOfWords = 1000; - final Random random = new Random(123456); - List<String> words = generateWords(numberOfWords, random); - - final String locale = "testRandomWords"; - final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(), - locale, mPrefs); - dict.isTest = true; - - addToDict(dict, words); - - try { - Log.d(TAG, "waiting for adding the word ..."); - Thread.sleep(2000); - } catch (InterruptedException e) { - Log.d(TAG, "InterruptedException: " + e); - } - - // write to file - dict.close(); - + File dictFile = null; try { - Log.d(TAG, "waiting for writing ..."); - Thread.sleep(5000); - } catch (InterruptedException e) { - Log.d(TAG, "InterruptedException: " + e); + Log.d(TAG, "This test can be used for profiling."); + Log.d(TAG, "Usage: please set UserHisotoryDictionary.PROFILE_SAVE_RESTORE to true."); + final int numberOfWords = 1000; + final Random random = new Random(123456); + List<String> words = generateWords(numberOfWords, random); + + final String locale = "testRandomWords"; + final String fileName = "UserHistoryDictionary." + locale + ".dict"; + dictFile = new File(getContext().getFilesDir(), fileName); + final UserHistoryDictionary dict = UserHistoryDictionary.getInstance(getContext(), + locale, mPrefs); + dict.isTest = true; + + addToDict(dict, words); + + try { + Log.d(TAG, "waiting for adding the word ..."); + Thread.sleep(2000); + } catch (InterruptedException e) { + Log.d(TAG, "InterruptedException: " + e); + } + + // write to file + dict.close(); + + try { + Log.d(TAG, "waiting for writing ..."); + Thread.sleep(5000); + } catch (InterruptedException e) { + Log.d(TAG, "InterruptedException: " + e); + } + } finally { + if (dictFile != null) { + dictFile.delete(); + } } } } diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java index 2f954318c..d080ed627 100644 --- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java @@ -271,7 +271,7 @@ public class BinaryDictIOTests extends AndroidTestCase { final String message) { File file = null; try { - file = File.createTempFile("runReadAndWrite", ".dict"); + file = File.createTempFile("runReadAndWrite", ".dict", getContext().getCacheDir()); } catch (IOException e) { Log.e(TAG, "IOException: " + e); } @@ -412,7 +412,7 @@ public class BinaryDictIOTests extends AndroidTestCase { final FormatSpec.FormatOptions formatOptions, final String message) { File file = null; try { - file = File.createTempFile("runReadUnigrams", ".dict"); + file = File.createTempFile("runReadUnigrams", ".dict", getContext().getCacheDir()); } catch (IOException e) { Log.e(TAG, "IOException: " + e); } @@ -484,7 +484,7 @@ public class BinaryDictIOTests extends AndroidTestCase { } if (header == null) return null; return BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize, - address - header.mHeaderSize, header.mFormatOptions); + address - header.mHeaderSize, header.mFormatOptions).mWord; } private long runGetTerminalPosition(final FusionDictionaryBufferInterface buffer, @@ -510,7 +510,8 @@ public class BinaryDictIOTests extends AndroidTestCase { public void testGetTerminalPosition() { File file = null; try { - file = File.createTempFile("testGetTerminalPosition", ".dict"); + file = File.createTempFile("testGetTerminalPosition", ".dict", + getContext().getCacheDir()); } catch (IOException e) { // do nothing } @@ -561,7 +562,7 @@ public class BinaryDictIOTests extends AndroidTestCase { public void testDeleteWord() { File file = null; try { - file = File.createTempFile("testDeleteWord", ".dict"); + file = File.createTempFile("testDeleteWord", ".dict", getContext().getCacheDir()); } catch (IOException e) { // do nothing } diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java new file mode 100644 index 000000000..318516845 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.makedict; + +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.ByteBufferWrapper; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.CharEncoding; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; +import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; +import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; + +import android.test.AndroidTestCase; +import android.test.MoreAsserts; +import android.util.Log; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Random; + +public class BinaryDictIOUtilsTests extends AndroidTestCase { + private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName(); + private static final FormatSpec.FormatOptions FORMAT_OPTIONS = + new FormatSpec.FormatOptions(3, true); + private static final int MAX_UNIGRAMS = 1500; + + private static final ArrayList<String> sWords = CollectionUtils.newArrayList(); + + private static final String[] CHARACTERS = { + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", + "\u00FC" /* ü */, "\u00E2" /* â */, "\u00F1" /* ñ */, // accented characters + "\u4E9C" /* 亜 */, "\u4F0A" /* 伊 */, "\u5B87" /* 宇 */, // kanji + "\uD841\uDE28" /* 𠘨 */, "\uD840\uDC0B" /* 𠀋 */, "\uD861\uDeD7" /* 𨛗 */ // surrogate pair + }; + + public BinaryDictIOUtilsTests() { + super(); + final Random random = new Random(123456); + sWords.clear(); + for (int i = 0; i < MAX_UNIGRAMS; ++i) { + sWords.add(generateWord(random.nextInt())); + } + } + + // Utilities for test + private String generateWord(final int value) { + final int lengthOfChars = CHARACTERS.length; + StringBuilder builder = new StringBuilder(""); + long lvalue = Math.abs((long)value); + while (lvalue > 0) { + builder.append(CHARACTERS[(int)(lvalue % lengthOfChars)]); + lvalue /= lengthOfChars; + } + if (builder.toString().equals("")) return "a"; + return builder.toString(); + } + + private static void printCharGroup(final CharGroupInfo info) { + Log.d(TAG, " CharGroup at " + info.mOriginalAddress); + Log.d(TAG, " flags = " + info.mFlags); + Log.d(TAG, " parentAddress = " + info.mParentAddress); + Log.d(TAG, " characters = " + new String(info.mCharacters, 0, + info.mCharacters.length)); + if (info.mFrequency != -1) Log.d(TAG, " frequency = " + info.mFrequency); + if (info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS) { + Log.d(TAG, " children address = no children address"); + } else { + Log.d(TAG, " children address = " + info.mChildrenAddress); + } + if (info.mShortcutTargets != null) { + for (final WeightedString ws : info.mShortcutTargets) { + Log.d(TAG, " shortcuts = " + ws.mWord); + } + } + if (info.mBigrams != null) { + for (final PendingAttribute attr : info.mBigrams) { + Log.d(TAG, " bigram = " + attr.mAddress); + } + } + Log.d(TAG, " end address = " + info.mEndAddress); + } + + private static void printNode(final FusionDictionaryBufferInterface buffer, + final FormatSpec.FormatOptions formatOptions) { + Log.d(TAG, "Node at " + buffer.position()); + final int count = BinaryDictInputOutput.readCharGroupCount(buffer); + Log.d(TAG, " charGroupCount = " + count); + for (int i = 0; i < count; ++i) { + final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, + buffer.position(), formatOptions); + printCharGroup(currentInfo); + } + if (formatOptions.mSupportsDynamicUpdate) { + final int forwardLinkAddress = buffer.readUnsignedInt24(); + Log.d(TAG, " forwardLinkAddress = " + forwardLinkAddress); + } + } + + private static void printBinaryFile(final FusionDictionaryBufferInterface buffer) + throws IOException, UnsupportedFormatException { + FileHeader header = BinaryDictInputOutput.readHeader(buffer); + while (buffer.position() < buffer.limit()) { + printNode(buffer, header.mFormatOptions); + } + } + + private int getWordPosition(final File file, final String word) { + int position = FormatSpec.NOT_VALID_WORD; + FileInputStream inStream = null; + try { + inStream = new FileInputStream(file); + final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper( + inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length())); + position = BinaryDictIOUtils.getTerminalPosition(buffer, word); + } catch (IOException e) { + } catch (UnsupportedFormatException e) { + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } + } + return position; + } + + private CharGroupInfo findWordFromFile(final File file, final String word) { + FileInputStream inStream = null; + CharGroupInfo info = null; + try { + inStream = new FileInputStream(file); + final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper( + inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length())); + info = BinaryDictIOUtils.findWordFromBuffer(buffer, word); + } catch (IOException e) { + } catch (UnsupportedFormatException e) { + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } + } + return info; + } + + // return amount of time to insert a word + private long insertAndCheckWord(final File file, final String word, final int frequency, + final boolean exist, final ArrayList<WeightedString> bigrams, + final ArrayList<WeightedString> shortcuts) { + RandomAccessFile raFile = null; + BufferedOutputStream outStream = null; + FusionDictionaryBufferInterface buffer = null; + long amountOfTime = -1; + try { + raFile = new RandomAccessFile(file, "rw"); + buffer = new ByteBufferWrapper(raFile.getChannel().map( + FileChannel.MapMode.READ_WRITE, 0, file.length())); + outStream = new BufferedOutputStream(new FileOutputStream(file, true)); + + if (!exist) { + assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); + } + final long now = System.nanoTime(); + BinaryDictIOUtils.insertWord(buffer, outStream, word, frequency, bigrams, shortcuts, + false, false); + amountOfTime = System.nanoTime() - now; + outStream.flush(); + MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); + outStream.close(); + raFile.close(); + } catch (IOException e) { + } catch (UnsupportedFormatException e) { + } finally { + if (outStream != null) { + try { + outStream.close(); + } catch (IOException e) { + // do nothing + } + } + if (raFile != null) { + try { + raFile.close(); + } catch (IOException e) { + // do nothing + } + } + } + return amountOfTime; + } + + private void deleteWord(final File file, final String word) { + RandomAccessFile raFile = null; + FusionDictionaryBufferInterface buffer = null; + try { + raFile = new RandomAccessFile(file, "rw"); + buffer = new ByteBufferWrapper(raFile.getChannel().map( + FileChannel.MapMode.READ_WRITE, 0, file.length())); + BinaryDictIOUtils.deleteWord(buffer, word); + } catch (IOException e) { + } catch (UnsupportedFormatException e) { + } finally { + if (raFile != null) { + try { + raFile.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + private void checkReverseLookup(final File file, final String word, final int position) { + FileInputStream inStream = null; + try { + inStream = new FileInputStream(file); + final FusionDictionaryBufferInterface buffer = new ByteBufferWrapper( + inStream.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, file.length())); + final FileHeader header = BinaryDictInputOutput.readHeader(buffer); + assertEquals(word, BinaryDictInputOutput.getWordAtAddress(buffer, header.mHeaderSize, + position - header.mHeaderSize, header.mFormatOptions)); + } catch (IOException e) { + } catch (UnsupportedFormatException e) { + } finally { + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + public void testInsertWord() { + File file = null; + try { + file = File.createTempFile("testInsertWord", ".dict", getContext().getCacheDir()); + } catch (IOException e) { + fail("IOException while creating temporary file: " + e); + } + + // set an initial dictionary. + final FusionDictionary dict = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); + dict.add("abcd", 10, null, false); + + try { + final FileOutputStream out = new FileOutputStream(file); + BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS); + out.close(); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } catch (UnsupportedFormatException e) { + fail("UnsupportedFormatException while writing an initial dictionary : " + e); + } + + MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd")); + insertAndCheckWord(file, "abcde", 10, false, null, null); + + insertAndCheckWord(file, "abcdefghijklmn", 10, false, null, null); + checkReverseLookup(file, "abcdefghijklmn", getWordPosition(file, "abcdefghijklmn")); + + insertAndCheckWord(file, "abcdabcd", 10, false, null, null); + checkReverseLookup(file, "abcdabcd", getWordPosition(file, "abcdabcd")); + + // update the existing word. + insertAndCheckWord(file, "abcdabcd", 15, true, null, null); + + // split 1 + insertAndCheckWord(file, "ab", 20, false, null, null); + + // split 2 + insertAndCheckWord(file, "ami", 30, false, null, null); + + deleteWord(file, "ami"); + assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "ami")); + + insertAndCheckWord(file, "abcdabfg", 30, false, null, null); + + deleteWord(file, "abcd"); + assertEquals(FormatSpec.NOT_VALID_WORD, getWordPosition(file, "abcd")); + } + + public void testInsertWordWithBigrams() { + File file = null; + try { + file = File.createTempFile("testInsertWordWithBigrams", ".dict", + getContext().getCacheDir()); + } catch (IOException e) { + fail("IOException while creating temporary file: " + e); + } + + // set an initial dictionary. + final FusionDictionary dict = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions(new HashMap<String,String>(), false, false)); + dict.add("abcd", 10, null, false); + dict.add("efgh", 15, null, false); + + try { + final FileOutputStream out = new FileOutputStream(file); + BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS); + out.close(); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } catch (UnsupportedFormatException e) { + fail("UnsupportedFormatException while writing an initial dictionary : " + e); + } + + final ArrayList<WeightedString> banana = new ArrayList<WeightedString>(); + banana.add(new WeightedString("banana", 10)); + + insertAndCheckWord(file, "banana", 0, false, null, null); + insertAndCheckWord(file, "recursive", 60, true, banana, null); + + final CharGroupInfo info = findWordFromFile(file, "recursive"); + int bananaPos = getWordPosition(file, "banana"); + assertNotNull(info.mBigrams); + assertEquals(info.mBigrams.size(), 1); + assertEquals(info.mBigrams.get(0).mAddress, bananaPos); + } + + public void testRandomWords() { + File file = null; + try { + file = File.createTempFile("testRandomWord", ".dict", getContext().getCacheDir()); + } catch (IOException e) { + } + assertNotNull(file); + + // set an initial dictionary. + final FusionDictionary dict = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, + false)); + dict.add("initial", 10, null, false); + + try { + final FileOutputStream out = new FileOutputStream(file); + BinaryDictInputOutput.writeDictionaryBinary(out, dict, FORMAT_OPTIONS); + out.close(); + } catch (IOException e) { + assertTrue(false); + } catch (UnsupportedFormatException e) { + assertTrue(false); + } + + long maxTimeToInsert = 0, sum = 0; + long minTimeToInsert = 100000000; // 1000000000 is an upper bound for minTimeToInsert. + int cnt = 0; + for (final String word : sWords) { + final long diff = insertAndCheckWord(file, word, + cnt % FormatSpec.MAX_TERMINAL_FREQUENCY, false, null, null); + maxTimeToInsert = Math.max(maxTimeToInsert, diff); + minTimeToInsert = Math.min(minTimeToInsert, diff); + sum += diff; + cnt++; + } + cnt = 0; + for (final String word : sWords) { + MoreAsserts.assertNotEqual(FormatSpec.NOT_VALID_WORD, getWordPosition(file, word)); + } + + Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms."); + Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms."); + Log.d(TAG, "avg = " + ((double)sum/MAX_UNIGRAMS/1000000) + " ms."); + } +} diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk index 5bd836a01..159c1c160 100644 --- a/tools/dicttool/Android.mk +++ b/tools/dicttool/Android.mk @@ -16,14 +16,18 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) -LATINIME_CORE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod/latin +LATINIME_BASE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod +LATINIME_CORE_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/latin +LATINIME_ANNOTATIONS_SOURCE_DIRECTORY := $(LATINIME_BASE_SOURCE_DIRECTORY)/annotations MAKEDICT_CORE_SOURCE_DIRECTORY := $(LATINIME_CORE_SOURCE_DIRECTORY)/makedict LOCAL_MAIN_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY)) LOCAL_TOOL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_ANNOTATIONS_SRC_FILES := $(call all-java-files-under,$(LATINIME_ANNOTATIONS_SOURCE_DIRECTORY)) LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \ $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \ $(call all-java-files-under,tests) \ + $(LOCAL_ANNOTATIONS_SRC_FILES) \ $(LATINIME_CORE_SOURCE_DIRECTORY)/Constants.java LOCAL_JAR_MANIFEST := etc/manifest.txt diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java deleted file mode 100644 index 8d4eb751b..000000000 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/AdditionalCommandList.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin.dicttool; - -public class AdditionalCommandList { - public static void populate() { - } -} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java deleted file mode 100644 index e59261706..000000000 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.latin.dicttool; - -public class Info extends Dicttool.Command { - public static final String COMMAND = "info"; - - public Info() { - } - - public String getHelp() { - return "info <filename>: prints various information about a dictionary file"; - } - - public void run() { - // TODO: implement this - if (mArgs.length < 1) { - throw new RuntimeException("Not enough arguments for command " + COMMAND); - } - System.out.println("Not implemented yet"); - } -} diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java new file mode 100644 index 000000000..c2c77d61a --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import org.xml.sax.SAXException; + +import java.io.File; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; + +import javax.xml.parsers.ParserConfigurationException; + +/** + * Class grouping utilities for offline dictionary making. + * + * Those should not be used on-device, essentially because they are quite + * liberal about I/O and performance. + */ +public final class BinaryDictOffdeviceUtils { + // Prefix and suffix are arbitrary, the values do not really matter + private final static String PREFIX = "dicttool"; + private final static String SUFFIX = ".tmp"; + + public final static String COMPRESSION = "compressed"; + public final static String ENCRYPTION = "encrypted"; + + private final static int MAX_DECODE_DEPTH = 8; + + public static class DecoderChainSpec { + ArrayList<String> mDecoderSpec = new ArrayList<String>(); + File mFile; + public DecoderChainSpec addStep(final String stepDescription) { + mDecoderSpec.add(stepDescription); + return this; + } + public String describeChain() { + final StringBuilder s = new StringBuilder("raw"); + for (final String step : mDecoderSpec) { + s.append(" > "); + s.append(step); + } + return s.toString(); + } + } + + public static void copy(final InputStream input, final OutputStream output) throws IOException { + final byte[] buffer = new byte[1000]; + final BufferedInputStream in = new BufferedInputStream(input); + final BufferedOutputStream out = new BufferedOutputStream(output); + for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer)) + output.write(buffer, 0, readBytes); + in.close(); + out.close(); + } + + /** + * Returns a decrypted/uncompressed binary dictionary. + * + * This will decrypt/uncompress any number of times as necessary until it finds the binary + * dictionary signature, and copy the decoded file to a temporary place. + * If this is not a binary dictionary, the method returns null. + */ + public static DecoderChainSpec getRawBinaryDictionaryOrNull(final File src) { + return getRawBinaryDictionaryOrNullInternal(new DecoderChainSpec(), src, 0); + } + + private static DecoderChainSpec getRawBinaryDictionaryOrNullInternal( + final DecoderChainSpec spec, final File src, final int depth) { + // Unfortunately the decoding scheme we use can consider any data to be encrypted + // and will product some output, meaning it's not possible to reliably detect encrypted + // data. Thus, some non-dictionary files (especially small) ones may successfully decrypt + // over and over, ending in a stack overflow. Hence we limit the depth at which we try + // decoding the file. + if (depth > MAX_DECODE_DEPTH) return null; + if (BinaryDictInputOutput.isBinaryDictionary(src)) { + spec.mFile = src; + return spec; + } + // It's not a raw dictionary - try to see if it's compressed. + final File uncompressedFile = tryGetUncompressedFile(src); + if (null != uncompressedFile) { + final DecoderChainSpec newSpec = + getRawBinaryDictionaryOrNullInternal(spec, uncompressedFile, depth + 1); + if (null == newSpec) return null; + return newSpec.addStep(COMPRESSION); + } + // It's not a compressed either - try to see if it's crypted. + final File decryptedFile = tryGetDecryptedFile(src); + if (null != decryptedFile) { + final DecoderChainSpec newSpec = + getRawBinaryDictionaryOrNullInternal(spec, decryptedFile, depth + 1); + if (null == newSpec) return null; + return newSpec.addStep(ENCRYPTION); + } + return null; + } + + /* Try to uncompress the file passed as an argument. + * + * If the file can be uncompressed, the uncompressed version is returned. Otherwise, null + * is returned. + */ + private static File tryGetUncompressedFile(final File src) { + try { + final File dst = File.createTempFile(PREFIX, SUFFIX); + dst.deleteOnExit(); + final FileOutputStream dstStream = new FileOutputStream(dst); + copy(Compress.getUncompressedStream(new BufferedInputStream(new FileInputStream(src))), + new BufferedOutputStream(dstStream)); // #copy() closes the streams + return dst; + } catch (IOException e) { + // Could not uncompress the file: presumably the file is simply not a compressed file + return null; + } + } + + /* Try to decrypt the file passed as an argument. + * + * If the file can be decrypted, the decrypted version is returned. Otherwise, null + * is returned. + */ + private static File tryGetDecryptedFile(final File src) { + try { + final File dst = File.createTempFile(PREFIX, SUFFIX); + dst.deleteOnExit(); + final FileOutputStream dstStream = new FileOutputStream(dst); + copy(Crypt.getDecryptedStream(new BufferedInputStream(new FileInputStream(src))), + dstStream); // #copy() closes the streams + return dst; + } catch (IOException e) { + // Could not decrypt the file: presumably the file is simply not a crypted file + return null; + } + } + + static void crash(final String filename, final Exception e) { + throw new RuntimeException("Can't read file " + filename, e); + } + + static FusionDictionary getDictionary(final String filename, final boolean report) { + final File file = new File(filename); + if (report) { + System.out.println("Dictionary : " + file.getAbsolutePath()); + System.out.println("Size : " + file.length() + " bytes"); + } + try { + if (XmlDictInputOutput.isXmlUnigramDictionary(filename)) { + if (report) System.out.println("Format : XML unigram list"); + return XmlDictInputOutput.readDictionaryXml( + new BufferedInputStream(new FileInputStream(file)), + null /* shortcuts */, null /* bigrams */); + } else if (CombinedInputOutput.isCombinedDictionary(filename)) { + if (report) System.out.println("Format : Combined format"); + return CombinedInputOutput.readDictionaryCombined( + new BufferedInputStream(new FileInputStream(file))); + } else { + final DecoderChainSpec decodedSpec = getRawBinaryDictionaryOrNull(file); + if (null == decodedSpec) { + crash(filename, new RuntimeException( + filename + " does not seem to be a dictionary file")); + } else { + final FileInputStream inStream = new FileInputStream(decodedSpec.mFile); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, decodedSpec.mFile.length()); + if (report) { + System.out.println("Format : Binary dictionary format"); + System.out.println("Packaging : " + decodedSpec.describeChain()); + System.out.println("Uncompressed size : " + decodedSpec.mFile.length()); + } + return BinaryDictInputOutput.readDictionaryBinary( + new BinaryDictInputOutput.ByteBufferWrapper(buffer), null); + } + } + } catch (IOException e) { + crash(filename, e); + } catch (SAXException e) { + crash(filename, e); + } catch (ParserConfigurationException e) { + crash(filename, e); + } catch (UnsupportedFormatException e) { + crash(filename, e); + } + return null; + } +} diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java new file mode 100644 index 000000000..092ee767f --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.makedict.Word; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.TreeSet; + +/** + * Reads and writes combined format for a FusionDictionary. + * + * All functions in this class are static. + */ +public class CombinedInputOutput { + + private static final String DICTIONARY_TAG = "dictionary"; + private static final String BIGRAM_TAG = "bigram"; + private static final String SHORTCUT_TAG = "shortcut"; + private static final String FREQUENCY_TAG = "f"; + private static final String WORD_TAG = "word"; + private static final String NOT_A_WORD_TAG = "not_a_word"; + private static final String WHITELIST_TAG = "whitelist"; + private static final String OPTIONS_TAG = "options"; + private static final String GERMAN_UMLAUT_PROCESSING_OPTION = "german_umlaut_processing"; + private static final String FRENCH_LIGATURE_PROCESSING_OPTION = "french_ligature_processing"; + private static final String COMMENT_LINE_STARTER = "#"; + + /** + * Basic test to find out whether the file is in the combined format or not. + * + * Concretely this only tests the header line. + * + * @param filename The name of the file to test. + * @return true if the file is in the combined format, false otherwise + */ + public static boolean isCombinedDictionary(final String filename) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(new File(filename))); + String firstLine = reader.readLine(); + while (firstLine.startsWith(COMMENT_LINE_STARTER)) { + firstLine = reader.readLine(); + } + return firstLine.matches("^" + DICTIONARY_TAG + "=[^:]+(:[^=]+=[^:]+)*"); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** + * Reads a dictionary from a combined format file. + * + * This is the public method that will read a combined file and return the corresponding memory + * representation. + * + * @param source the file to read the data from. + * @return the in-memory representation of the dictionary. + */ + public static FusionDictionary readDictionaryCombined(final InputStream source) + throws IOException { + final BufferedReader reader = new BufferedReader(new InputStreamReader(source, "UTF-8")); + String headerLine = reader.readLine(); + while (headerLine.startsWith(COMMENT_LINE_STARTER)) { + headerLine = reader.readLine(); + } + final String header[] = headerLine.split(","); + final HashMap<String, String> attributes = new HashMap<String, String>(); + for (String item : header) { + final String keyValue[] = item.split("="); + if (2 != keyValue.length) { + throw new RuntimeException("Wrong header format : " + headerLine); + } + attributes.put(keyValue[0], keyValue[1]); + } + + final boolean processUmlauts = + GERMAN_UMLAUT_PROCESSING_OPTION.equals(attributes.get(OPTIONS_TAG)); + final boolean processLigatures = + FRENCH_LIGATURE_PROCESSING_OPTION.equals(attributes.get(OPTIONS_TAG)); + attributes.remove(OPTIONS_TAG); + final FusionDictionary dict = new FusionDictionary(new Node(), new DictionaryOptions( + attributes, processUmlauts, processLigatures)); + + String line; + String word = null; + int freq = 0; + boolean isNotAWord = false; + ArrayList<WeightedString> bigrams = new ArrayList<WeightedString>(); + ArrayList<WeightedString> shortcuts = new ArrayList<WeightedString>(); + while (null != (line = reader.readLine())) { + if (line.startsWith(COMMENT_LINE_STARTER)) continue; + final String args[] = line.trim().split(","); + if (args[0].matches(WORD_TAG + "=.*")) { + if (null != word) { + dict.add(word, freq, shortcuts.isEmpty() ? null : shortcuts, isNotAWord); + for (WeightedString s : bigrams) { + dict.setBigram(word, s.mWord, s.mFrequency); + } + } + if (!shortcuts.isEmpty()) shortcuts = new ArrayList<WeightedString>(); + if (!bigrams.isEmpty()) bigrams = new ArrayList<WeightedString>(); + isNotAWord = false; + for (String param : args) { + final String params[] = param.split("=", 2); + if (2 != params.length) throw new RuntimeException("Wrong format : " + line); + if (WORD_TAG.equals(params[0])) { + word = params[1]; + } else if (FREQUENCY_TAG.equals(params[0])) { + freq = Integer.parseInt(params[1]); + } else if (NOT_A_WORD_TAG.equals(params[0])) { + isNotAWord = "true".equals(params[1]); + } + } + } else if (args[0].matches(SHORTCUT_TAG + "=.*")) { + String shortcut = null; + int shortcutFreq = 0; + for (String param : args) { + final String params[] = param.split("=", 2); + if (2 != params.length) throw new RuntimeException("Wrong format : " + line); + if (SHORTCUT_TAG.equals(params[0])) { + shortcut = params[1]; + } else if (FREQUENCY_TAG.equals(params[0])) { + shortcutFreq = WHITELIST_TAG.equals(params[1]) + ? FormatSpec.SHORTCUT_WHITELIST_FREQUENCY + : Integer.parseInt(params[1]); + } + } + if (null != shortcut) { + shortcuts.add(new WeightedString(shortcut, shortcutFreq)); + } else { + throw new RuntimeException("Wrong format : " + line); + } + } else if (args[0].matches(BIGRAM_TAG + "=.*")) { + String secondWordOfBigram = null; + int bigramFreq = 0; + for (String param : args) { + final String params[] = param.split("=", 2); + if (2 != params.length) throw new RuntimeException("Wrong format : " + line); + if (BIGRAM_TAG.equals(params[0])) { + secondWordOfBigram = params[1]; + } else if (FREQUENCY_TAG.equals(params[0])) { + bigramFreq = Integer.parseInt(params[1]); + } + } + if (null != secondWordOfBigram) { + bigrams.add(new WeightedString(secondWordOfBigram, bigramFreq)); + } else { + throw new RuntimeException("Wrong format : " + line); + } + } + } + if (null != word) { + dict.add(word, freq, shortcuts.isEmpty() ? null : shortcuts, isNotAWord); + for (WeightedString s : bigrams) { + dict.setBigram(word, s.mWord, s.mFrequency); + } + } + + return dict; + } + + /** + * Writes a dictionary to a combined file. + * + * @param destination a destination stream to write to. + * @param dict the dictionary to write. + */ + public static void writeDictionaryCombined(Writer destination, FusionDictionary dict) + throws IOException { + final TreeSet<Word> set = new TreeSet<Word>(); + for (Word word : dict) { + set.add(word); // This for ordering by frequency, then by asciibetic order + } + final HashMap<String, String> options = dict.mOptions.mAttributes; + destination.write(DICTIONARY_TAG + "="); + if (options.containsKey(DICTIONARY_TAG)) { + destination.write(options.get(DICTIONARY_TAG)); + options.remove(DICTIONARY_TAG); + } + if (dict.mOptions.mGermanUmlautProcessing) { + destination.write("," + OPTIONS_TAG + "=" + GERMAN_UMLAUT_PROCESSING_OPTION); + } else if (dict.mOptions.mFrenchLigatureProcessing) { + destination.write("," + OPTIONS_TAG + "=" + FRENCH_LIGATURE_PROCESSING_OPTION); + } + for (final String key : dict.mOptions.mAttributes.keySet()) { + final String value = dict.mOptions.mAttributes.get(key); + destination.write("," + key + "=" + value); + } + destination.write("\n"); + for (Word word : set) { + destination.write(" " + WORD_TAG + "=" + word.mWord + "," + + FREQUENCY_TAG + "=" + word.mFrequency + + (word.mIsNotAWord ? "," + NOT_A_WORD_TAG + "=true\n" : "\n")); + if (null != word.mShortcutTargets) { + for (WeightedString target : word.mShortcutTargets) { + destination.write(" " + SHORTCUT_TAG + "=" + target.mWord + "," + + FREQUENCY_TAG + "=" + target.mFrequency + "\n"); + } + } + if (null != word.mBigrams) { + for (WeightedString bigram : word.mBigrams) { + destination.write(" " + BIGRAM_TAG + "=" + bigram.mWord + "," + + FREQUENCY_TAG + "=" + bigram.mFrequency + "\n"); + } + } + } + destination.close(); + } +} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java index d16b069fe..0e0095bd6 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/CommandList.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CommandList.java @@ -19,8 +19,13 @@ package com.android.inputmethod.latin.dicttool; public class CommandList { public static void populate() { Dicttool.addCommand("info", Info.class); + Dicttool.addCommand("diff", Diff.class); Dicttool.addCommand("compress", Compress.Compressor.class); Dicttool.addCommand("uncompress", Compress.Uncompressor.class); + Dicttool.addCommand("encrypt", Crypt.Encrypter.class); + Dicttool.addCommand("decrypt", Crypt.Decrypter.class); + Dicttool.addCommand("package", Package.Packager.class); + Dicttool.addCommand("unpackage", Package.Unpackager.class); Dicttool.addCommand("makedict", Makedict.class); } } diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java index 3cb0a12c4..b7f48b522 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin.dicttool; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -26,24 +28,19 @@ import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; public class Compress { + private Compress() { + // This container class is not publicly instantiable. + } - private static OutputStream getCompressedStream(final OutputStream out) + public static OutputStream getCompressedStream(final OutputStream out) throws java.io.IOException { return new GZIPOutputStream(out); } - private static InputStream getUncompressedStream(final InputStream in) throws IOException { + public static InputStream getUncompressedStream(final InputStream in) throws IOException { return new GZIPInputStream(in); } - public static void copy(final InputStream input, final OutputStream output) throws IOException { - final byte[] buffer = new byte[1000]; - for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) - output.write(buffer, 0, readBytes); - input.close(); - output.close(); - } - static public class Compressor extends Dicttool.Command { public static final String COMMAND = "compress"; public static final String STDIN_OR_STDOUT = "-"; @@ -51,11 +48,13 @@ public class Compress { public Compressor() { } + @Override public String getHelp() { return COMMAND + " <src_filename> <dst_filename>: " + "Compresses a file using gzip compression"; } + @Override public void run() throws IOException { if (mArgs.length > 2) { throw new RuntimeException("Too many arguments for command " + COMMAND); @@ -63,10 +62,10 @@ public class Compress { final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT; final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT; final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in - : new FileInputStream(new File(inFilename)); + : new BufferedInputStream(new FileInputStream(new File(inFilename))); final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out - : new FileOutputStream(new File(outFilename)); - copy(input, new GZIPOutputStream(output)); + : new BufferedOutputStream(new FileOutputStream(new File(outFilename))); + BinaryDictOffdeviceUtils.copy(input, new GZIPOutputStream(output)); } } @@ -77,11 +76,13 @@ public class Compress { public Uncompressor() { } + @Override public String getHelp() { return COMMAND + " <src_filename> <dst_filename>: " + "Uncompresses a file compressed with gzip compression"; } + @Override public void run() throws IOException { if (mArgs.length > 2) { throw new RuntimeException("Too many arguments for command " + COMMAND); @@ -89,10 +90,10 @@ public class Compress { final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT; final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT; final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in - : new FileInputStream(new File(inFilename)); + : new BufferedInputStream(new FileInputStream(new File(inFilename))); final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out - : new FileOutputStream(new File(outFilename)); - copy(new GZIPInputStream(input), output); + : new BufferedOutputStream(new FileOutputStream(new File(outFilename))); + BinaryDictOffdeviceUtils.copy(new GZIPInputStream(input), output); } } } diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java new file mode 100644 index 000000000..f8990231e --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Crypt.java @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import java.io.InputStream; +import java.io.OutputStream; + +public class Crypt { + private Crypt() { + // This container class is not publicly instantiable. + } + + public static OutputStream getCryptedStream(final OutputStream out) { + // Encryption is not supported + return out; + } + + public static InputStream getDecryptedStream(final InputStream in) { + // Decryption is not supported + return in; + } + + static public class Encrypter extends Dicttool.Command { + public static final String COMMAND = "encrypt"; + + public Encrypter() { + } + + public String getHelp() { + return COMMAND + " <src_filename> <dst_filename>: Encrypts a file"; + } + + public void run() { + throw new UnsupportedOperationException(); + } + } + + static public class Decrypter extends Dicttool.Command { + public static final String COMMAND = "decrypt"; + + public Decrypter() { + } + + public String getHelp() { + return COMMAND + " <src_filename> <dst_filename>: Decrypts a file"; + } + + public void run() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java index 4f8874985..cc890f60c 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java @@ -22,6 +22,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary; import com.android.inputmethod.latin.makedict.MakedictLog; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; +import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -51,13 +52,16 @@ public class DictionaryMaker { private static final String OPTION_INPUT_SHORTCUT_XML = "-c"; private static final String OPTION_OUTPUT_BINARY = "-d"; private static final String OPTION_OUTPUT_XML = "-x"; + private static final String OPTION_OUTPUT_COMBINED = "-o"; private static final String OPTION_HELP = "-h"; public final String mInputBinary; + public final String mInputCombined; public final String mInputUnigramXml; public final String mInputShortcutXml; public final String mInputBigramXml; public final String mOutputBinary; public final String mOutputXml; + public final String mOutputCombined; public final int mOutputBinaryFormatVersion; private void checkIntegrity() throws IOException { @@ -65,28 +69,38 @@ public class DictionaryMaker { checkHasAtLeastOneOutput(); checkNotSameFile(mInputBinary, mOutputBinary); checkNotSameFile(mInputBinary, mOutputXml); + checkNotSameFile(mInputCombined, mOutputBinary); + checkNotSameFile(mInputCombined, mOutputXml); checkNotSameFile(mInputUnigramXml, mOutputBinary); checkNotSameFile(mInputUnigramXml, mOutputXml); + checkNotSameFile(mInputUnigramXml, mOutputCombined); checkNotSameFile(mInputShortcutXml, mOutputBinary); checkNotSameFile(mInputShortcutXml, mOutputXml); + checkNotSameFile(mInputShortcutXml, mOutputCombined); checkNotSameFile(mInputBigramXml, mOutputBinary); checkNotSameFile(mInputBigramXml, mOutputXml); + checkNotSameFile(mInputBigramXml, mOutputCombined); checkNotSameFile(mOutputBinary, mOutputXml); + checkNotSameFile(mOutputBinary, mOutputCombined); + checkNotSameFile(mOutputXml, mOutputCombined); } private void checkHasExactlyOneInput() { - if (null == mInputUnigramXml && null == mInputBinary) { + if (null == mInputUnigramXml && null == mInputBinary && null == mInputCombined) { throw new RuntimeException("No input file specified"); - } else if (null != mInputUnigramXml && null != mInputBinary) { - throw new RuntimeException("Both input XML and binary specified"); - } else if (null != mInputBinary && null != mInputBigramXml) { - throw new RuntimeException("Cannot specify a binary input and a separate bigram " - + "file"); + } else if ((null != mInputUnigramXml && null != mInputBinary) + || (null != mInputUnigramXml && null != mInputCombined) + || (null != mInputBinary && null != mInputCombined)) { + throw new RuntimeException("Several input files specified"); + } else if ((null != mInputBinary || null != mInputCombined) + && (null != mInputBigramXml || null != mInputShortcutXml)) { + throw new RuntimeException("Separate bigrams/shortcut files are only supported" + + " with XML input (other formats include bigrams and shortcuts already)"); } } private void checkHasAtLeastOneOutput() { - if (null == mOutputBinary && null == mOutputXml) { + if (null == mOutputBinary && null == mOutputXml && null == mOutputCombined) { throw new RuntimeException("No output specified"); } } @@ -110,17 +124,16 @@ public class DictionaryMaker { public static String getHelp() { return "Usage: makedict " + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts_and_whitelist.xml>] " + + "| [-s <combined format input]" + "| [-s <binary input>] [-d <binary output>] [-x <xml output>] " + + " [-o <combined output>]" + "[-1] [-2] [-3]\n" + "\n" + " Converts a source dictionary file to one or several outputs.\n" + " Source can be an XML file, with an optional XML bigrams file, or a\n" + " binary dictionary file.\n" - + " Binary version 1 (Ice Cream Sandwich), 2 (Jelly Bean), 3 and XML outputs\n" - + " are supported. All three can be output at the same time, but the same\n" - + " output format cannot be specified several times. The behavior is\n" - + " unspecified if the same file is specified for input and output, or for\n" - + " several outputs."; + + " Binary version 1 (Ice Cream Sandwich), 2 (Jelly Bean), 3, XML and\n" + + " combined format outputs are supported."; } public Arguments(String[] argsArray) throws IOException { @@ -129,11 +142,13 @@ public class DictionaryMaker { displayHelp(); } String inputBinary = null; + String inputCombined = null; String inputUnigramXml = null; String inputShortcutXml = null; String inputBigramXml = null; String outputBinary = null; String outputXml = null; + String outputCombined = null; int outputBinaryFormatVersion = 2; // the default version is 2. while (!args.isEmpty()) { @@ -157,10 +172,15 @@ public class DictionaryMaker { String filename = args.get(0); args.remove(0); if (OPTION_INPUT_SOURCE.equals(arg)) { - if (BinaryDictInputOutput.isBinaryDictionary(filename)) { + if (XmlDictInputOutput.isXmlUnigramDictionary(filename)) { + inputUnigramXml = filename; + } else if (CombinedInputOutput.isCombinedDictionary(filename)) { + inputCombined = filename; + } else if (BinaryDictInputOutput.isBinaryDictionary(filename)) { inputBinary = filename; } else { - inputUnigramXml = filename; + throw new IllegalArgumentException( + "Unknown format for file " + filename); } } else if (OPTION_INPUT_SHORTCUT_XML.equals(arg)) { inputShortcutXml = filename; @@ -170,6 +190,8 @@ public class DictionaryMaker { outputBinary = filename; } else if (OPTION_OUTPUT_XML.equals(arg)) { outputXml = filename; + } else if (OPTION_OUTPUT_COMBINED.equals(arg)) { + outputCombined = filename; } else { throw new IllegalArgumentException("Unknown option : " + arg); } @@ -178,6 +200,8 @@ public class DictionaryMaker { if (null == inputBinary && null == inputUnigramXml) { if (BinaryDictInputOutput.isBinaryDictionary(arg)) { inputBinary = arg; + } else if (CombinedInputOutput.isCombinedDictionary(arg)) { + inputCombined = arg; } else { inputUnigramXml = arg; } @@ -190,11 +214,13 @@ public class DictionaryMaker { } mInputBinary = inputBinary; + mInputCombined = inputCombined; mInputUnigramXml = inputUnigramXml; mInputShortcutXml = inputShortcutXml; mInputBigramXml = inputBigramXml; mOutputBinary = outputBinary; mOutputXml = outputXml; + mOutputCombined = outputCombined; mOutputBinaryFormatVersion = outputBinaryFormatVersion; checkIntegrity(); } @@ -219,6 +245,8 @@ public class DictionaryMaker { SAXException, FileNotFoundException { if (null != args.mInputBinary) { return readBinaryFile(args.mInputBinary); + } else if (null != args.mInputCombined) { + return readCombinedFile(args.mInputCombined); } else if (null != args.mInputUnigramXml) { return readXmlFile(args.mInputUnigramXml, args.mInputShortcutXml, args.mInputBigramXml); } else { @@ -258,6 +286,32 @@ public class DictionaryMaker { } /** + * Read a dictionary from the name of a combined file. + * + * @param combinedFilename the name of the file in the combined format. + * @return the read dictionary. + * @throws FileNotFoundException if the file can't be found + * @throws IOException if the input file can't be read + */ + private static FusionDictionary readCombinedFile(final String combinedFilename) + throws FileNotFoundException, IOException { + FileInputStream inStream = null; + try { + final File file = new File(combinedFilename); + inStream = new FileInputStream(file); + return CombinedInputOutput.readDictionaryCombined(inStream); + } finally { + if (null != inStream) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** * Read a dictionary from a unigram XML file, and optionally a bigram XML file. * * @param unigramXmlFilename the name of the unigram XML file. May not be null. @@ -298,6 +352,9 @@ public class DictionaryMaker { if (null != args.mOutputXml) { writeXmlDictionary(args.mOutputXml, dict); } + if (null != args.mOutputCombined) { + writeCombinedDictionary(args.mOutputCombined, dict); + } } /** @@ -328,6 +385,21 @@ public class DictionaryMaker { */ private static void writeXmlDictionary(final String outputFilename, final FusionDictionary dict) throws FileNotFoundException, IOException { - XmlDictInputOutput.writeDictionaryXml(new FileWriter(outputFilename), dict); + XmlDictInputOutput.writeDictionaryXml(new BufferedWriter(new FileWriter(outputFilename)), + dict); + } + + /** + * Write the dictionary in the combined format to the specified filename. + * + * @param outputFilename the name of the file to write to. + * @param dict the dictionary to write. + * @throws FileNotFoundException if the output file can't be created. + * @throws IOException if the output file can't be written to. + */ + private static void writeCombinedDictionary(final String outputFilename, + final FusionDictionary dict) throws FileNotFoundException, IOException { + CombinedInputOutput.writeDictionaryCombined( + new BufferedWriter(new FileWriter(outputFilename)), dict); } } diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java index bf417fb5a..7b311c3ec 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java @@ -33,7 +33,6 @@ public class Dicttool { new HashMap<String, Class<? extends Command>>(); static { CommandList.populate(); - AdditionalCommandList.populate(); } public static void addCommand(final String commandName, final Class<? extends Command> cls) { sCommands.put(commandName, cls); @@ -80,6 +79,7 @@ public class Dicttool { } catch (Exception e) { System.out.println("Exception while processing command " + command.getClass().getSimpleName() + " : " + e); + e.printStackTrace(); return; } } diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java new file mode 100644 index 000000000..5c3e87e10 --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Diff.java @@ -0,0 +1,214 @@ +/** + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.makedict.Word; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; + +public class Diff extends Dicttool.Command { + public static final String COMMAND = "diff"; + + public Diff() { + } + + @Override + public String getHelp() { + return COMMAND + " [-p] <dict> <dict> : shows differences between two dictionaries.\n" + + " If -p (plumbing) option is given, produce output suitable for a script"; + } + + @Override + public void run() { + if (mArgs.length < 2) { + throw new RuntimeException("Not enough arguments for command " + COMMAND); + } + final boolean plumbing; + if ("-p".equals(mArgs[0])) { + plumbing = true; + mArgs = Arrays.copyOfRange(mArgs, 1, mArgs.length); + if (mArgs.length != 2) { // There should be only 2 arguments left + throw new RuntimeException("Wrong number of arguments for command " + COMMAND); + } + } else { + plumbing = false; + } + final FusionDictionary dict0 = + BinaryDictOffdeviceUtils.getDictionary(mArgs[0], false /* report */); + if (null == dict0) throw new RuntimeException("Can't read dictionary " + mArgs[0]); + final FusionDictionary dict1 = + BinaryDictOffdeviceUtils.getDictionary(mArgs[1], false /* report */); + if (null == dict1) throw new RuntimeException("Can't read dictionary " + mArgs[1]); + if (!plumbing) { + System.out.println("Header :"); + diffHeaders(dict0, dict1); + if (languageDiffers(dict0, dict1)) { + // We only check for the language here. The rationale is that one may meaningfully + // diff a en_US with a en_GB dictionary, but someone who diffs a de dict with a + // pt_BR dict is almost certainly only interested in header-level diff, and the word + // diff would be very large, meaningless, and annoying. + return; + } + System.out.println("Body :"); + } + diffWords(dict0, dict1); + } + + private static boolean languageDiffers(final FusionDictionary dict0, + final FusionDictionary dict1) { + // If either of the dictionaries have no locale, assume it's okay + if (null == dict0.mOptions.mAttributes.get("locale")) return false; + if (null == dict1.mOptions.mAttributes.get("locale")) return false; + final String dict0Lang = dict0.mOptions.mAttributes.get("locale").split("_", 3)[0]; + final String dict1Lang = dict1.mOptions.mAttributes.get("locale").split("_", 3)[0]; + return !dict0Lang.equals(dict1Lang); + } + + private static void diffHeaders(final FusionDictionary dict0, final FusionDictionary dict1) { + boolean hasDifferences = false; + if (dict0.mOptions.mFrenchLigatureProcessing != dict1.mOptions.mFrenchLigatureProcessing) { + System.out.println(" French ligature processing : " + + dict0.mOptions.mFrenchLigatureProcessing + " <=> " + + dict1.mOptions.mFrenchLigatureProcessing); + hasDifferences = true; + } + else if (dict0.mOptions.mGermanUmlautProcessing != dict1.mOptions.mGermanUmlautProcessing) { + System.out.println(" German umlaut processing : " + + dict0.mOptions.mGermanUmlautProcessing + " <=> " + + dict1.mOptions.mGermanUmlautProcessing); + hasDifferences = true; + } + final HashMap<String, String> options1 = + new HashMap<String, String>(dict1.mOptions.mAttributes); + for (final String optionKey : dict0.mOptions.mAttributes.keySet()) { + if (!dict0.mOptions.mAttributes.get(optionKey).equals( + dict1.mOptions.mAttributes.get(optionKey))) { + System.out.println(" " + optionKey + " : " + + dict0.mOptions.mAttributes.get(optionKey) + " <=> " + + dict1.mOptions.mAttributes.get(optionKey)); + hasDifferences = true; + } + options1.remove(optionKey); + } + for (final String optionKey : options1.keySet()) { + System.out.println(" " + optionKey + " : null <=> " + options1.get(optionKey)); + hasDifferences = true; + } + if (!hasDifferences) { + System.out.println(" No differences"); + } + } + + private static void diffWords(final FusionDictionary dict0, final FusionDictionary dict1) { + boolean hasDifferences = false; + for (final Word word0 : dict0) { + final CharGroup word1 = FusionDictionary.findWordInTree(dict1.mRoot, word0.mWord); + if (null == word1) { + // This word is not in dict1 + System.out.println("Deleted: " + word0.mWord + " " + word0.mFrequency); + hasDifferences = true; + } else { + // We found the word. Compare frequencies, shortcuts, bigrams + if (word0.mFrequency != word1.getFrequency()) { + System.out.println("Freq changed: " + word0.mWord + " " + word0.mFrequency + + " -> " + word1.getFrequency()); + hasDifferences = true; + } + if (word0.mIsNotAWord != word1.getIsNotAWord()) { + System.out.println("Not a word: " + word0.mWord + " " + word0.mIsNotAWord + + " -> " + word1.getIsNotAWord()); + hasDifferences = true; + } + if (word0.mIsBlacklistEntry != word1.getIsBlacklistEntry()) { + System.out.println("Blacklist: " + word0.mWord + " " + word0.mIsBlacklistEntry + + " -> " + word1.getIsBlacklistEntry()); + hasDifferences = true; + } + hasDifferences |= hasAttributesDifferencesAndPrintThemIfAny(word0.mWord, + "Bigram", word0.mBigrams, word1.getBigrams()); + hasDifferences |= hasAttributesDifferencesAndPrintThemIfAny(word0.mWord, + "Shortcut", word0.mShortcutTargets, word1.getShortcutTargets()); + } + } + for (final Word word1 : dict1) { + final CharGroup word0 = FusionDictionary.findWordInTree(dict0.mRoot, word1.mWord); + if (null == word0) { + // This word is not in dict0 + System.out.println("Added: " + word1.mWord + " " + word1.mFrequency); + hasDifferences = true; + } + } + if (!hasDifferences) { + System.out.println(" No differences"); + } + } + + private static boolean hasAttributesDifferencesAndPrintThemIfAny(final String word, + final String type, final ArrayList<WeightedString> list0, + final ArrayList<WeightedString> list1) { + if (null == list1) { + if (null == list0) return false; + for (final WeightedString attribute0 : list0) { + System.out.println(type + " removed: " + word + " " + attribute0.mWord + " " + + attribute0.mFrequency); + } + return true; + } + boolean hasDifferences = false; + if (null != list0) { + for (final WeightedString attribute0 : list0) { + // The following tests with #equals(). The WeightedString#equals() method returns + // true if both the string and the frequency are the same. + if (!list1.contains(attribute0)) { + hasDifferences = true; + // Search for a word with the same string but a different frequency + boolean foundString = false; + for (final WeightedString attribute1 : list1) { + if (attribute0.mWord.equals(attribute1.mWord)) { + System.out.println(type + " freq changed: " + word + " " + + attribute0.mWord + " " + attribute0.mFrequency + " -> " + + attribute1.mFrequency); + list1.remove(attribute1); + foundString = true; + break; + } + } + if (!foundString) { + // We come here if we haven't found any matching string. + System.out.println(type + " removed: " + word + " " + attribute0.mWord + " " + + attribute0.mFrequency); + } + } else { + list1.remove(attribute0); + } + } + } + // We removed any matching word that we found, so now list1 only contains words that + // are not included in list0. + for (final WeightedString attribute1 : list1) { + hasDifferences = true; + System.out.println(type + " added: " + word + " " + attribute1.mWord + " " + + attribute1.mFrequency); + } + return hasDifferences; + } +} diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java new file mode 100644 index 000000000..f2894544f --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Info.java @@ -0,0 +1,128 @@ +/** + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup; +import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.makedict.Word; + +import java.util.Arrays; +import java.util.ArrayList; + +public class Info extends Dicttool.Command { + public static final String COMMAND = "info"; + + public Info() { + } + + @Override + public String getHelp() { + return COMMAND + " <filename>: prints various information about a dictionary file"; + } + + private static void showInfo(final FusionDictionary dict, final boolean plumbing) { + System.out.println("Header attributes :"); + System.out.print(dict.mOptions.toString(2, plumbing)); + int wordCount = 0; + int bigramCount = 0; + int shortcutCount = 0; + int whitelistCount = 0; + for (final Word w : dict) { + ++wordCount; + if (null != w.mBigrams) { + bigramCount += w.mBigrams.size(); + } + if (null != w.mShortcutTargets) { + shortcutCount += w.mShortcutTargets.size(); + for (WeightedString shortcutTarget : w.mShortcutTargets) { + if (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY == shortcutTarget.mFrequency) { + ++whitelistCount; + } + } + } + } + System.out.println("Words in the dictionary : " + wordCount); + System.out.println("Bigram count : " + bigramCount); + System.out.println("Shortcuts : " + shortcutCount + " (out of which " + whitelistCount + + " whitelist entries)"); + } + + private static void showWordInfo(final FusionDictionary dict, final String word, + final boolean plumbing) { + final CharGroup group = FusionDictionary.findWordInTree(dict.mRoot, word); + if (null == group) { + System.out.println(word + " is not in the dictionary"); + return; + } + System.out.println("Word: " + word); + System.out.println(" Freq: " + group.getFrequency()); + if (group.getIsNotAWord()) { + System.out.println(" Is not a word"); + } + if (group.getIsBlacklistEntry()) { + System.out.println(" Is a blacklist entry"); + } + final ArrayList<WeightedString> shortcutTargets = group.getShortcutTargets(); + if (null == shortcutTargets || shortcutTargets.isEmpty()) { + System.out.println(" No shortcuts"); + } else { + for (final WeightedString shortcutTarget : shortcutTargets) { + System.out.println(" Shortcut target: " + shortcutTarget.mWord + " (" + + (FormatSpec.SHORTCUT_WHITELIST_FREQUENCY == shortcutTarget.mFrequency + ? "whitelist" : shortcutTarget.mFrequency) + ")"); + } + } + final ArrayList<WeightedString> bigrams = group.getBigrams(); + if (null == bigrams || bigrams.isEmpty()) { + System.out.println(" No bigrams"); + } else { + for (final WeightedString bigram : bigrams) { + System.out.println(" Bigram: " + bigram.mWord + " (" + bigram.mFrequency + ")"); + } + } + } + + @Override + public void run() { + if (mArgs.length < 1) { + throw new RuntimeException("Not enough arguments for command " + COMMAND); + } + final boolean plumbing; + if ("-p".equals(mArgs[0])) { + plumbing = true; + mArgs = Arrays.copyOfRange(mArgs, 1, mArgs.length); + if (mArgs.length != 1) { // There should be only 1 argument left + throw new RuntimeException("Wrong number of arguments for command " + COMMAND); + } + } else { + plumbing = false; + } + final String filename = mArgs[0]; + final boolean hasWordArguments = (1 == mArgs.length); + final FusionDictionary dict = BinaryDictOffdeviceUtils.getDictionary(filename, + hasWordArguments /* report */); + if (hasWordArguments) { + showInfo(dict, plumbing); + } else { + for (int i = 1; i < mArgs.length; ++i) { + showWordInfo(dict, mArgs[i], plumbing); + } + } + } +} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Makedict.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Makedict.java index c004cfbe4..808e1d4c8 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Makedict.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Makedict.java @@ -29,10 +29,12 @@ public class Makedict extends Dicttool.Command { public Makedict() { } + @Override public String getHelp() { return DictionaryMaker.Arguments.getHelp(); } + @Override public void run() throws FileNotFoundException, IOException, ParserConfigurationException, SAXException, UnsupportedFormatException { DictionaryMaker.main(mArgs); diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java new file mode 100644 index 000000000..b29480764 --- /dev/null +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java @@ -0,0 +1,92 @@ +/** + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +public class Package { + private Package() { + // This container class is not publicly instantiable. + } + + static public class Packager extends Dicttool.Command { + public static final String COMMAND = "package"; + private final static String PREFIX = "dicttool"; + private final static String SUFFIX = ".tmp"; + + public Packager() { + } + + public String getHelp() { + return COMMAND + " <src_filename> <dst_filename>: Package a file for distribution"; + } + + public void run() throws IOException { + if (mArgs.length != 2) { + throw new RuntimeException("Too many/too few arguments for command " + COMMAND); + } + final File intermediateFile = File.createTempFile(PREFIX, SUFFIX); + try { + final Compress.Compressor compressCommand = new Compress.Compressor(); + compressCommand.setArgs(new String[] { mArgs[0], intermediateFile.getPath() }); + compressCommand.run(); + final Crypt.Encrypter cryptCommand = new Crypt.Encrypter(); + cryptCommand.setArgs(new String[] { intermediateFile.getPath(), mArgs[1] }); + cryptCommand.run(); + } finally { + intermediateFile.delete(); + } + } + } + + static public class Unpackager extends Dicttool.Command { + public static final String COMMAND = "unpackage"; + + public Unpackager() { + } + + public String getHelp() { + return COMMAND + " <src_filename> <dst_filename>: Detects how a file is packaged and\n" + + "decrypts/uncompresses as necessary to produce a raw binary file."; + } + + public void run() throws FileNotFoundException, IOException { + if (mArgs.length != 2) { + throw new RuntimeException("Too many/too few arguments for command " + COMMAND); + } + final BinaryDictOffdeviceUtils.DecoderChainSpec decodedSpec = + BinaryDictOffdeviceUtils.getRawBinaryDictionaryOrNull(new File(mArgs[0])); + if (null == decodedSpec) { + System.out.println(mArgs[0] + " does not seem to be a dictionary"); + return; + } + System.out.println("Packaging : " + decodedSpec.describeChain()); + System.out.println("Uncompressed size : " + decodedSpec.mFile.length()); + final FileOutputStream dstStream = new FileOutputStream(new File(mArgs[1])); + BinaryDictOffdeviceUtils.copy(new BufferedInputStream( + new FileInputStream(decodedSpec.mFile)), new BufferedOutputStream(dstStream)); + } + } +} diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java index 252c3d655..1fd2cba7a 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java @@ -22,6 +22,10 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.Node; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import com.android.inputmethod.latin.makedict.Word; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Writer; @@ -52,8 +56,6 @@ public class XmlDictInputOutput { private static final String WORD_ATTR = "word"; private static final String NOT_A_WORD_ATTR = "not_a_word"; - private static final int SHORTCUT_ONLY_DEFAULT_FREQ = 1; - private static final String OPTIONS_KEY = "options"; private static final String GERMAN_UMLAUT_PROCESSING_OPTION = "german_umlaut_processing"; private static final String FRENCH_LIGATURE_PROCESSING_OPTION = "french_ligature_processing"; @@ -63,12 +65,9 @@ public class XmlDictInputOutput { */ static private class UnigramHandler extends DefaultHandler { // Parser states - private static final int NONE = 0; private static final int START = 1; private static final int WORD = 2; - private static final int BIGRAM = 4; - private static final int END = 5; - private static final int UNKNOWN = 6; + private static final int UNKNOWN = 3; FusionDictionary mDictionary; int mState; // the state of the parser @@ -264,6 +263,35 @@ public class XmlDictInputOutput { } /** + * Basic test to find out whether the file is in the unigram XML format or not. + * + * Concretely this only tests the header line. + * + * @param filename The name of the file to test. + * @return true if the file is in the unigram XML format, false otherwise + */ + public static boolean isXmlUnigramDictionary(final String filename) { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(new File(filename))); + final String firstLine = reader.readLine(); + return firstLine.matches("^\\s*<wordlist .*>\\s*$"); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // do nothing + } + } + } + } + + /** * Reads a dictionary from an XML file. * * This is the public method that will parse an XML file and return the corresponding memory diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/MakedictLog.java b/tools/dicttool/src/com/android/inputmethod/latin/makedict/MakedictLog.java index 7eccff2b4..7eccff2b4 100644 --- a/tools/dicttool/src/android/inputmethod/latin/dicttool/MakedictLog.java +++ b/tools/dicttool/src/com/android/inputmethod/latin/makedict/MakedictLog.java diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java new file mode 100644 index 000000000..554bd2478 --- /dev/null +++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.dicttool; + +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import junit.framework.TestCase; + +import java.io.File; +import java.io.BufferedOutputStream; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Unit tests for BinaryDictOffdeviceUtilsTests + */ +public class BinaryDictOffdeviceUtilsTests extends TestCase { + private static final int TEST_FREQ = 37; // Some arbitrary value unlikely to happen by chance + + public void testGetRawDictWorks() throws IOException, UnsupportedFormatException { + // Create a thrice-compressed dictionary file. + final FusionDictionary dict = new FusionDictionary(new Node(), + new DictionaryOptions(new HashMap<String, String>(), + false /* germanUmlautProcessing */, false /* frenchLigatureProcessing */)); + dict.add("foo", TEST_FREQ, null, false /* isNotAWord */); + dict.add("fta", 1, null, false /* isNotAWord */); + dict.add("ftb", 1, null, false /* isNotAWord */); + dict.add("bar", 1, null, false /* isNotAWord */); + dict.add("fool", 1, null, false /* isNotAWord */); + + final File dst = File.createTempFile("testGetRawDict", ".tmp"); + dst.deleteOnExit(); + final OutputStream out = Compress.getCompressedStream( + Compress.getCompressedStream( + Compress.getCompressedStream( + new BufferedOutputStream(new FileOutputStream(dst))))); + + BinaryDictInputOutput.writeDictionaryBinary(out, dict, new FormatOptions(2, false)); + + // Test for an actually compressed dictionary and its contents + final BinaryDictOffdeviceUtils.DecoderChainSpec decodeSpec = + BinaryDictOffdeviceUtils.getRawBinaryDictionaryOrNull(dst); + for (final String step : decodeSpec.mDecoderSpec) { + assertEquals("Wrong decode spec", BinaryDictOffdeviceUtils.COMPRESSION, step); + } + assertEquals("Wrong decode spec", 3, decodeSpec.mDecoderSpec.size()); + final FileInputStream inStream = new FileInputStream(decodeSpec.mFile); + final ByteBuffer buffer = inStream.getChannel().map( + FileChannel.MapMode.READ_ONLY, 0, decodeSpec.mFile.length()); + final FusionDictionary resultDict = BinaryDictInputOutput.readDictionaryBinary( + new BinaryDictInputOutput.ByteBufferWrapper(buffer), + null /* dict : an optional dictionary to add words to, or null */); + assertEquals("Dictionary can't be read back correctly", + resultDict.findWordInTree(resultDict.mRoot, "foo").getFrequency(), TEST_FREQ); + } + + public void testGetRawDictFails() throws IOException { + // Randomly create some 4k file containing garbage + final File dst = File.createTempFile("testGetRawDict", ".tmp"); + dst.deleteOnExit(); + final OutputStream out = new BufferedOutputStream(new FileOutputStream(dst)); + for (int i = 0; i < 1024; ++i) { + out.write(0x12345678); + } + out.close(); + + // Test that a random data file actually fails + assertNull("Wrongly identified data file", + BinaryDictOffdeviceUtils.getRawBinaryDictionaryOrNull(dst)); + + final File gzDst = File.createTempFile("testGetRawDict", ".tmp"); + gzDst.deleteOnExit(); + final OutputStream gzOut = + Compress.getCompressedStream(new BufferedOutputStream(new FileOutputStream(gzDst))); + for (int i = 0; i < 1024; ++i) { + gzOut.write(0x12345678); + } + gzOut.close(); + + // Test that a compressed random data file actually fails + assertNull("Wrongly identified data file", + BinaryDictOffdeviceUtils.getRawBinaryDictionaryOrNull(gzDst)); + } +} diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java index 88589b815..096902879 100644 --- a/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java +++ b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java @@ -19,24 +19,15 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import junit.framework.TestCase; + import java.util.ArrayList; import java.util.HashMap; -import junit.framework.TestCase; - /** * Unit tests for BinaryDictInputOutput. */ public class BinaryDictInputOutputTest extends TestCase { - - public void setUp() throws Exception { - super.setUp(); - } - - public void tearDown() throws Exception { - super.tearDown(); - } - // Test the flattened array contains the expected number of nodes, and // that it does not contain any duplicates. public void testFlattenNodes() { @@ -55,5 +46,4 @@ public class BinaryDictInputOutputTest extends TestCase { assertFalse("Flattened array contained the same node twice", result.contains(n)); } } - } diff --git a/tools/dicttool/tests/etc/test-dicttool.sh b/tools/dicttool/tests/etc/test-dicttool.sh index 8834611cd..0f3ed6d62 100755 --- a/tools/dicttool/tests/etc/test-dicttool.sh +++ b/tools/dicttool/tests/etc/test-dicttool.sh @@ -13,4 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/../common/obj/JAVA_LIBRARIES/dicttool_intermediates/classes junit.textui.TestRunner com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest +java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/framework/dicttool_aosp.jar junit.textui.TestRunner com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest +java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/framework/dicttool_aosp.jar junit.textui.TestRunner com.android.inputmethod.latin.dicttool.BinaryDictOffdeviceUtilsTests diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl index 774094cd7..00637afbc 100644 --- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl +++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl @@ -19,6 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.Context; import android.content.res.Resources; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; @@ -64,7 +65,7 @@ public final class KeyboardTextsSet { loadStringResourcesInternal(context, RESOURCE_NAMES, R.string.english_ime_name); } - /* package for test */ + @UsedForTesting void loadStringResourcesInternal(Context context, final String[] resourceNames, int referenceId) { final Resources res = context.getResources(); diff --git a/tools/maketext/res/values-ar/donottranslate-more-keys.xml b/tools/maketext/res/values-ar/donottranslate-more-keys.xml index 970e85cff..8028bb292 100644 --- a/tools/maketext/res/values-ar/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-ar/donottranslate-more-keys.xml @@ -105,6 +105,13 @@ <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK --> <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> --> <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,“,”,«|»,»|«,‘,’,‚,‛</string> + <!-- U+00A2: "¢" CENT SIGN + U+00A3: "£" POUND SIGN + U+20AC: "€" EURO SIGN + U+00A5: "¥" YEN SIGN + U+20B1: "₱" PESO SIGN + U+FDFC: "﷼" RIAL SIGN --> + <string name="more_keys_for_currency_dollar">¢,£,€,¥,₱,﷼</string> <!-- U+0655: "ٕ" ARABIC HAMZA BELOW U+0654: "ٔ" ARABIC HAMZA ABOVE U+0652: "ْ" ARABIC SUKUN diff --git a/tools/maketext/res/values-es/donottranslate-more-keys.xml b/tools/maketext/res/values-es/donottranslate-more-keys.xml index 586dbade8..961193bfa 100644 --- a/tools/maketext/res/values-es/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-es/donottranslate-more-keys.xml @@ -74,4 +74,7 @@ <string name="more_keys_for_tablet_comma">"!,¡"</string> <!-- U+00BF: "¿" INVERTED QUESTION MARK --> <string name="more_keys_for_tablet_period">"\?,¿"</string> + <string name="keylabel_for_apostrophe">\"</string> + <string name="keyhintlabel_for_apostrophe">\'</string> + <string name="more_keys_for_apostrophe">\'</string> </resources> diff --git a/tools/maketext/res/values-fa/donottranslate-more-keys.xml b/tools/maketext/res/values-fa/donottranslate-more-keys.xml index 3519eea39..544426ce3 100644 --- a/tools/maketext/res/values-fa/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-fa/donottranslate-more-keys.xml @@ -112,6 +112,15 @@ <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK --> <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> --> <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,“,”,«|»,»|«,‘,’,‚,‛</string> + <!-- U+FDFC: "﷼" RIAL SIGN + U+060B: "؋" AFGHANI SIGN + U+00A2: "¢" CENT SIGN + U+00A3: "£" POUND SIGN + U+20AC: "€" EURO SIGN + U+00A5: "¥" YEN SIGN + U+20B1: "₱" PESO SIGN --> + <string name="keylabel_for_currency_generic">﷼</string> + <string name="more_keys_for_currency_generic">$,¢,€,£,¥,₱,؋</string> <!-- U+0655: "ٕ" ARABIC HAMZA BELOW U+0652: "ْ" ARABIC SUKUN U+0651: "ّ" ARABIC SHADDA diff --git a/tools/maketext/res/values-hi/donottranslate-more-keys.xml b/tools/maketext/res/values-hi/donottranslate-more-keys.xml index 19bcb9dda..00fd5bf3b 100644 --- a/tools/maketext/res/values-hi/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-hi/donottranslate-more-keys.xml @@ -48,4 +48,6 @@ <string name="additional_more_keys_for_symbols_8">8</string> <string name="additional_more_keys_for_symbols_9">9</string> <string name="additional_more_keys_for_symbols_0">0</string> + <!-- U+20B9: "₹" INDIAN RUPEE SIGN --> + <string name="keylabel_for_currency_generic">₹</string> </resources> diff --git a/tools/maketext/res/values-iw/donottranslate-more-keys.xml b/tools/maketext/res/values-iw/donottranslate-more-keys.xml index ad209edde..4e7ad48b6 100644 --- a/tools/maketext/res/values-iw/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-iw/donottranslate-more-keys.xml @@ -50,4 +50,6 @@ <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK --> <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,“,”,„,‟,«|»,»|«;,‘,’,‚,‛</string> --> <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,“,”,«|»,»|«,‘,’,‚,‛</string> + <!-- U+20AA: "₪" NEW SHEQEL SIGN --> + <string name="keylabel_for_currency_generic">₪</string> </resources> diff --git a/tools/maketext/res/values-mn/donottranslate-more-keys.xml b/tools/maketext/res/values-mn/donottranslate-more-keys.xml new file mode 100644 index 000000000..c6736e485 --- /dev/null +++ b/tools/maketext/res/values-mn/donottranslate-more-keys.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- U+20AE: "₮" TUGRIK SIGN --> + <string name="keylabel_for_currency_generic">₮</string> +</resources> diff --git a/tools/maketext/res/values-th/donottranslate-more-keys.xml b/tools/maketext/res/values-th/donottranslate-more-keys.xml new file mode 100644 index 000000000..1f2a8207d --- /dev/null +++ b/tools/maketext/res/values-th/donottranslate-more-keys.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** +** Copyright 2012, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT --> + <string name="keylabel_for_currency_generic">฿</string> +</resources> diff --git a/tools/maketext/res/values-uk/donottranslate-more-keys.xml b/tools/maketext/res/values-uk/donottranslate-more-keys.xml index 6d4b5f9e1..6df9c0a4c 100644 --- a/tools/maketext/res/values-uk/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-uk/donottranslate-more-keys.xml @@ -34,4 +34,6 @@ <string name="more_keys_for_east_slavic_row2_1">ї</string> <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN --> <string name="more_keys_for_cyrillic_soft_sign">ъ</string> + <!-- U+20B4: "₴" HRYVNIA SIGN --> + <string name="keylabel_for_currency_generic">₴</string> </resources> diff --git a/tools/maketext/res/values-vi/donottranslate-more-keys.xml b/tools/maketext/res/values-vi/donottranslate-more-keys.xml index 6ef1c6bc5..fa98ea9e1 100644 --- a/tools/maketext/res/values-vi/donottranslate-more-keys.xml +++ b/tools/maketext/res/values-vi/donottranslate-more-keys.xml @@ -92,4 +92,6 @@ <string name="more_keys_for_y">ỳ,ý,ỷ,ỹ,ỵ</string> <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE --> <string name="more_keys_for_d">đ</string> + <!-- U+20AB: "₫" DONG SIGN --> + <string name="keylabel_for_currency_generic">₫</string> </resources> diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml index 4d7100b8c..4d8ad104a 100644 --- a/tools/maketext/res/values/donottranslate-more-keys.xml +++ b/tools/maketext/res/values/donottranslate-more-keys.xml @@ -73,9 +73,8 @@ U+00A5: "¥" YEN SIGN U+20B1: "₱" PESO SIGN --> <string name="more_keys_for_currency_dollar">¢,£,€,¥,₱</string> - <string name="more_keys_for_currency_euro">¢,£,$,¥,₱</string> - <string name="more_keys_for_currency_pound">¢,$,€,¥,₱</string> - <string name="more_keys_for_currency_general">¢,$,€,£,¥,₱</string> + <string name="keylabel_for_currency_generic">$</string> + <string name="more_keys_for_currency_generic">$,¢,€,£,¥,₱</string> <string name="more_keys_for_punctuation">"!fixedColumnOrder!8,\",\',#,-,:,!,\\,,\?,\@,&,\\%,+,;,/,(,)"</string> <!-- U+2020: "†" DAGGER U+2021: "‡" DOUBLE DAGGER diff --git a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java b/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java index 37ac0d006..50a0c9958 100644 --- a/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java +++ b/tools/maketext/src/com/android/inputmethod/latin/maketext/MoreKeysResources.java @@ -158,6 +158,12 @@ public class MoreKeysResources { out.format(" private static final String[] " + ARRAY_NAME_FOR_LANGUAGE + " = {\n", language); final StringResourceMap resMap = mResourcesMap.get(language); + for (final StringResource res : resMap.getResources()) { + if (!defaultResMap.contains(res.mName)) { + throw new RuntimeException(res.mName + " in " + language + + " doesn't have default resource"); + } + } dumpTextsInternal(out, resMap, defaultResMap); out.format(" };\n\n"); } |