diff options
195 files changed, 5116 insertions, 2248 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/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/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_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_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/key_styles_currency.xml b/java/res/xml/key_styles_currency.xml index 6dea16f89..1dadbe033 100644 --- a/java/res/xml/key_styles_currency.xml +++ b/java/res/xml/key_styles_currency.xml @@ -53,13 +53,13 @@ 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. --> + <!-- 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. --> <!-- Note: Some locales may not have country code, and it it supposed to indicate the country where the language originally/mainly spoken. --> <case - latin:localeCode="da|de|es|el|fi|fr|it|nl|sk|sl|pt_PT|tr" + latin:localeCode="da|de|es|el|fi|fr|it|nl|sk|sl|sv|pt_PT|tr" > <include latin:keyboardLayout="@xml/key_styles_currency_euro" /> 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 3d360a8f0..ff8998ecf 100644 --- a/java/res/xml/method.xml +++ b/java/res/xml/method.xml @@ -36,6 +36,7 @@ en_GB: English Great Britain/qwerty eo: Esperanto/spanish es: Spanish/spanish + es_419: Spanish Latin America/qwerty et: Estonian/nordic fa: Persian/arabic fi: Finnish/nordic @@ -184,6 +185,13 @@ /> <subtype android:icon="@drawable/ic_subtype_keyboard" android:label="@string/subtype_generic" + android:subtypeId="1648333446" + android:imeSubtypeLocale="es_419" + android:imeSubtypeMode="keyboard" + android:imeSubtypeExtraValue="KeyboardLayoutSet=qwerty,AsciiCapable" + /> + <subtype android:icon="@drawable/ic_subtype_keyboard" + android:label="@string/subtype_generic" android:subtypeId="-332580523" android:imeSubtypeLocale="et" android:imeSubtypeMode="keyboard" 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_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/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..926f3ec28 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -29,7 +29,7 @@ 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.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.ImfUtils; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; @@ -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) { @@ -147,6 +146,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { 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(); @@ -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..e60a8a942 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 ""; 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..2e4a0eeff 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); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 3bb272f8c..e08536b43 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(); 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/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..a7024d1d8 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 fullWordMultiplier, 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, FULL_WORD_SCORE_MULTIPLIER, + 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 b0b65edb6..bed31a7d1 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..ecb61b46f 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -165,14 +165,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(); } /** 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/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index 88d0c09dd..8207bc47f 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -59,12 +59,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 +73,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 +87,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..16cc1b35f 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; @@ -69,7 +68,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 +171,7 @@ public class ExpandableDictionary extends Dictionary { } } - public void setRequiresReload(boolean reload) { + public void setRequiresReload(final boolean reload) { synchronized (mUpdatingLock) { mRequiresReload = reload; } @@ -202,8 +201,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 +247,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 +266,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 +275,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 +303,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 +318,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 +343,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 +366,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 +430,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 +471,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 +523,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 +541,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 +573,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 +604,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 +624,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 +697,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/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 cf7eea70b..5f87c8c91 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -60,6 +60,7 @@ 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; @@ -132,14 +133,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 Resources mResources; private SharedPreferences mPrefs; - /* package for tests */ final KeyboardSwitcher mKeyboardSwitcher; + @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeSwitcher mSubtypeSwitcher; private boolean mShouldSwitchToLastSubtype = true; @@ -163,8 +164,6 @@ 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; @@ -173,7 +172,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction new DictionaryPackInstallBroadcastReceiver(this); // Keeps track of most recently inserted text (multi-character key) for reverting - private CharSequence mEnteredText; + private String mEnteredText; private boolean mIsAutoCorrectionIndicatorOn; @@ -424,7 +423,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } // 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. @@ -438,7 +437,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()); } @@ -1109,7 +1107,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); @@ -1146,7 +1144,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) { @@ -1162,8 +1160,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); @@ -1177,12 +1175,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 @@ -1277,7 +1275,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 @@ -1294,7 +1292,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; @@ -1309,13 +1307,13 @@ 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++; @@ -1323,29 +1321,29 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mShouldSwitchToLastSubtype = 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); } @@ -1381,10 +1379,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(); @@ -1395,7 +1393,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()); @@ -1403,16 +1401,16 @@ 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) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } mConnection.commitText(text, 1); mConnection.endBatchEdit(); // 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; } @@ -1541,8 +1539,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); @@ -1560,15 +1558,15 @@ 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; } mWordComposer.setBatchInputWord(batchInputText); mConnection.beginBatchEdit(); if (SPACE_STATE_PHANTOM == mSpaceState) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } mConnection.setComposingText(batchInputText, 1); mExpectingUpdateSelection = true; @@ -1578,8 +1576,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; @@ -1590,8 +1588,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; } @@ -1695,7 +1693,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 @@ -1724,7 +1722,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Sanity check throw new RuntimeException("Should not be composing here"); } - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } // NOTE: isCursorTouchingWord() is a blocking IPC call, so it often takes several @@ -1738,7 +1736,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 @@ -1747,15 +1745,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 @@ -1802,11 +1799,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (SPACE_STATE_PHANTOM == spaceState && mCurrentSettings.isPhantomSpacePromotingSymbol(primaryCode)) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } sendKeyCodePoint(primaryCode); - if (Keyboard.CODE_SPACE == primaryCode) { + if (Constants.CODE_SPACE == primaryCode) { if (mCurrentSettings.isSuggestionsRequested(mDisplayOrientation)) { if (maybeDoubleSpace()) { mSpaceState = SPACE_STATE_DOUBLE; @@ -1851,7 +1848,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; @@ -1868,7 +1865,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(); @@ -1938,7 +1935,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(); @@ -1946,7 +1943,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, @@ -1955,7 +1952,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 @@ -1985,21 +1982,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(); @@ -2013,9 +2005,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)) { @@ -2046,7 +2038,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()) { @@ -2071,7 +2063,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction int firstChar = Character.codePointAt(suggestion, 0); if ((!mCurrentSettings.isWeakSpaceStripper(firstChar)) && (!mCurrentSettings.isWeakSpaceSwapper(firstChar))) { - sendKeyCodePoint(Keyboard.CODE_SPACE); + sendKeyCodePoint(Constants.CODE_SPACE); } } @@ -2116,7 +2108,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) { @@ -2131,13 +2123,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 @@ -2156,7 +2148,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; @@ -2171,19 +2163,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; } @@ -2208,9 +2199,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); @@ -2220,9 +2211,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 @@ -2256,7 +2247,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. @@ -2272,13 +2263,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 @@ -2295,16 +2279,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. @@ -2324,7 +2308,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(); } } }; diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 21441369e..d1c96d42a 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; @@ -398,7 +397,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); @@ -466,19 +465,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; } /** @@ -511,19 +513,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 @@ -540,7 +543,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; @@ -558,7 +561,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); @@ -569,8 +572,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; @@ -586,7 +589,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); } } @@ -612,7 +615,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; @@ -664,7 +667,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/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/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..ac3364902 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -28,7 +28,12 @@ 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) +LOCAL_CFLAGS += -Winline +endif # To suppress compiler warnings for unused variables/functions used for debug features etc. LOCAL_CFLAGS += -Wno-unused-parameter -Wno-unused-function @@ -50,6 +55,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..004665a3b 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -44,9 +44,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) { + jstring sourceDir, jlong dictOffset, jlong dictSize, jint fullWordMultiplier, + jint maxWordLength, jint maxWords, jint maxPredictions) { PROF_OPEN; PROF_START(66); const jsize sourceDirUtf8Length = env->GetStringUTFLength(sourceDir); @@ -71,7 +70,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 +84,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 +94,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; @@ -121,7 +120,7 @@ static jlong latinime_BinaryDictionary_open(JNIEnv *env, jobject object, #endif // USE_MMAP_FOR_DICTIONARY } else { dictionary = new Dictionary(dictBuf, static_cast<int>(dictSize), fd, adjust, - typedLetterMultiplier, fullWordMultiplier, maxWordLength, maxWords, maxPredictions); + fullWordMultiplier, maxWordLength, maxWords, maxPredictions); } PROF_END(66); PROF_CLOSE; @@ -133,7 +132,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 +162,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 +179,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 +219,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 +272,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;JJIIII)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/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..9d27cadad 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; @@ -894,9 +895,7 @@ 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; } } // 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 ea0f0ef70..40bc958d1 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -30,17 +30,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 +51,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 +60,11 @@ static inline void dumpResult( AKLOGI("-------------------------"); } -static inline void dumpWord(const unsigned short *word, const int length) { +static 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 +77,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 +122,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 +202,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 +224,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 @@ -282,6 +269,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 @@ -344,8 +333,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) @@ -392,6 +381,14 @@ 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])) + +#ifdef __GNUC__ +#define AK_FORCE_INLINE __attribute__((always_inline)) __inline__ +#else // __GNUC__ +#define AK_FORCE_INLINE inline +#endif // __GNUC__ + // 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..5fbe0461b 100644 --- a/native/jni/src/dictionary.cpp +++ b/native/jni/src/dictionary.cpp @@ -30,13 +30,12 @@ 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) + int fullWordMultiplier, 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, fullWordMultiplier, 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..2ca00ab63 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 fullWordMultiplier, + 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..db79bb616 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]; @@ -104,7 +105,10 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi 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", @@ -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,6 +171,7 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi prevPrevNearKeysDistances = prevNearKeysDistances; prevNearKeysDistances = currentNearKeysDistances; currentNearKeysDistances = tmp; + sumAngle = 0.0f; } } } @@ -161,43 +179,68 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi } 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); + // 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(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]; + + // 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 < mInputSize - 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 { - length = 0; - duration = 1; + const float speed = static_cast<float>(length) / static_cast<float>(duration); + mRelativeSpeeds[i] = speed / averageSpeed; } - const float speed = static_cast<float>(length) / static_cast<float>(duration); - mRelativeSpeeds[i] = speed / averageSpeed; + } + + // Direction calculation. + mDirections.resize(mInputSize - 1); + for (int i = max(0, lastSavedInputSize - 1); i < mInputSize - 1; ++i) { + mDirections[i] = getDirection(i, i + 1); + } + + } + + if (DEBUG_GEO_FULL) { + for (int i = 0; i < mInputSize; ++i) { + AKLOGI("Sampled(%d): x = %d, y = %d, time = %d", i, mInputXs[i], mInputYs[i], + mTimes[i]); } } if (mInputSize > 0) { const int keyCount = mProximityInfo->getKeyCount(); mNearKeysVector.resize(mInputSize); + mSearchKeysVector.resize(mInputSize); mDistanceCache.resize(mInputSize * keyCount); for (int i = lastSavedInputSize; i < mInputSize; ++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; @@ -207,29 +250,53 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi 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 < mInputSize; ++i) { + if (i >= lastSavedInputSize) { + mSearchKeysVector[i].reset(); + } + for (int j = max(i, lastSavedInputSize); j < mInputSize; ++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 < mInputSize; ++i) { + sampledX << mInputXs[i]; + sampledY << mInputYs[i]; + if (i != mInputSize - 1) { + sampledX << ";"; + sampledY << ";"; + } + } + AKLOGI("\n%s, %s,\n%s, %s,\n", originalX.str().c_str(), originalY.str().c_str(), + sampledX.str().c_str(), sampledY.str().c_str()); + } // end /////////////////////// @@ -239,12 +306,12 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi && 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]; + 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 +319,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,7 +335,7 @@ 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); } } } @@ -294,7 +362,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 +400,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 = mInputXs.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; 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 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,18 +450,18 @@ 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(); 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. @@ -419,42 +472,24 @@ bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeChar 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, mInputXs.back(), mInputYs.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, mInputXs.back(), mInputYs.back(), + getDistanceInt(x, y, mInputXs.back(), mInputYs.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); @@ -503,22 +538,92 @@ int ProximityInfoState::getDuration(const int index) const { 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); } @@ -538,8 +643,9 @@ int32_t ProximityInfoState::getAllPossibleChars( 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,6 +663,12 @@ int32_t ProximityInfoState::getAllPossibleChars( return newFilterSize; } +bool ProximityInfoState::isKeyInSerchKeysAfterIndex(const int index, const int keyId) const { + ASSERT(keyId >= 0); + ASSERT(index >= 0 && index < mInputSize); + return mSearchKeysVector[index].test(keyId); +} + void ProximityInfoState::popInputData() { mInputXs.pop_back(); mInputYs.pop_back(); @@ -565,4 +677,389 @@ void ProximityInfoState::popInputData() { mInputIndice.pop_back(); } +float ProximityInfoState::getDirection(const int index0, const int index1) const { + if (index0 < 0 || index0 > mInputSize - 1) { + return 0.0f; + } + if (index1 < 0 || index1 > mInputSize - 1) { + return 0.0f; + } + const int x1 = mInputXs[index0]; + const int y1 = mInputYs[index0]; + const int x2 = mInputXs[index1]; + const int y2 = mInputYs[index1]; + return getAngle(x1, y1, x2, y2); +} + +float ProximityInfoState::getPointAngle(const int index) const { + if (index <= 0 || index >= mInputSize - 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 > mInputSize - 1) { + return 0.0f; + } + if (index1 < 0 || index1 > mInputSize - 1) { + return 0.0f; + } + if (index2 < 0 || index2 > mInputSize - 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 > mInputSize - 1) { + return 0.0f; + } + if (to < 0 || to > mInputSize - 1) { + return 0.0f; + } + const int x0 = mInputXs[from]; + const int y0 = mInputYs[from]; + const int x1 = mInputXs[to]; + const int y1 = mInputYs[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(mInputSize); + // Calculates probabilities of using a point as a correlated point with the character + // for each point. + for (int i = start; i < mInputSize; ++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 == mInputSize - 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 != mInputSize - 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 == mInputSize - 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 != mInputSize - 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 == mInputSize - 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 < mInputSize; ++i) { + std::stringstream sstream; + sstream << i << ", "; + sstream << "("<< mInputXs[i] << ", "; + sstream << ", "<< mInputYs[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 < mInputSize; ++i) { + for (int j = i + 1; j < mInputSize; ++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 < mInputSize; ++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 < mInputSize); + ASSERT(0 <= index1 && index1 < mInputSize); + + 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 < mInputSize && 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 < mInputSize); + 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..39a238889 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(), + mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mDirections(), + mCharProbabilities(), mNearKeysVector(), mSearchKeysVector(), mTouchPositionCorrectionEnabled(false), mInputSize(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; } } @@ -81,90 +83,25 @@ class ProximityInfoState { inline bool existsAdjacentProximityChars(const int index) const { if (index < 0 || index >= mInputSize) return false; - const int currentChar = getPrimaryCharAt(index); + 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 < mInputSize && 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 +109,13 @@ class ProximityInfoState { return mTouchPositionCorrectionEnabled; } - inline bool sameAsTyped(const unsigned short *word, int length) const { + inline bool sameAsTyped(const int *word, int length) const { if (length != mInputSize) { 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; @@ -213,7 +150,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 +164,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 +194,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); @@ -248,7 +208,7 @@ class ProximityInfoState { return mInputXs.size() > 0 && mInputYs.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 +219,14 @@ 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); // const const ProximityInfo *mProximityInfo; @@ -286,12 +248,23 @@ class ProximityInfoState { 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 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..d134a47e6 100644 --- a/native/jni/src/unigram_dictionary.cpp +++ b/native/jni/src/unigram_dictionary.cpp @@ -41,14 +41,11 @@ 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 fullWordMultiplier, + int maxWordLength, int maxWords, const unsigned int flags) + : DICT_ROOT(streamStart), MAX_WORD_LENGTH(maxWordLength), MAX_WORDS(maxWords), + FULL_WORD_MULTIPLIER(fullWordMultiplier), // TODO : remove this variable. + ROOT_POS(0), MAX_DIGRAPH_SEARCH_DEPTH(DEFAULT_MAX_DIGRAPH_SEARCH_DEPTH), FLAGS(flags) { if (DEBUG_DICT) { AKLOGI("UnigramDictionary - constructor"); } @@ -57,13 +54,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 +101,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 +124,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 +135,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 +151,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 +171,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 +184,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 +192,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 +216,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 +228,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 +281,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 +300,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 +365,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 +373,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 +401,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 +441,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 +484,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 +561,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 +589,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 +675,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 +689,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 +711,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 +749,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 +762,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 +824,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 +849,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 +867,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 +895,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 +909,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..248b09db1 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 fullWordMultiplier, 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,19 @@ 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..7d0c4d17d 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(); @@ -175,15 +119,18 @@ class WordsPriorityQueue { DUMP_WORD(mHighestSuggestedWord->mWord, mHighestSuggestedWord->mWordLength); } - float getHighestNormalizedScore(const unsigned short *before, const int beforeLength, - unsigned short **outWord, int *outScore, int *outLength) { + 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/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/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(); |