diff options
56 files changed, 1400 insertions, 2011 deletions
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml index fb973f336..42f343a42 100644 --- a/java/AndroidManifest.xml +++ b/java/AndroidManifest.xml @@ -109,7 +109,7 @@ <receiver android:name=".DictionaryPackInstallBroadcastReceiver"> <intent-filter> - <action android:name="com.android.inputmethod.dictionarypack.UNKNOWN_CLIENT" /> + <action android:name="com.android.inputmethod.dictionarypack.aosp.UNKNOWN_CLIENT" /> </intent-filter> </receiver> @@ -129,7 +129,7 @@ <intent-filter> <action android:name="android.intent.action.DOWNLOAD_COMPLETE" /> <action android:name="android.intent.action.DATE_CHANGED" /> - <action android:name="com.android.inputmethod.latin.dictionarypack.UPDATE_NOW" /> + <action android:name="com.android.inputmethod.dictionarypack.aosp.UPDATE_NOW" /> </intent-filter> </receiver> diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml index 77a76b5d6..f1212f93e 100644 --- a/java/res/values-af/strings.xml +++ b/java/res/values-af/strings.xml @@ -183,7 +183,7 @@ <string name="setup_step2_instruction" msgid="9141481964870023336">"Volgende, kies \"<xliff:g id="APPLICATION_NAME">%s</xliff:g>\" as jou aktiewe teks-invoermetode."</string> <string name="setup_step2_action" msgid="1660330307159824337">"Wissel invoermetodes"</string> <string name="setup_step3_title" msgid="3154757183631490281">"Veels geluk, jy\'s gereed!"</string> - <string name="setup_step3_instruction" msgid="8025981829605426000">"Nou kan jy in al jou gunsteling programme tik met <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string> + <string name="setup_step3_instruction" msgid="8025981829605426000">"Nou kan jy in al jou gunstelingprogramme tik met <xliff:g id="APPLICATION_NAME">%s</xliff:g>."</string> <string name="setup_step3_action" msgid="600879797256942259">"Stel bykomende tale op"</string> <string name="setup_finish_action" msgid="276559243409465389">"Klaar"</string> <string name="show_setup_wizard_icon" msgid="5008028590593710830">"Wys program-ikoon"</string> diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml index cbb07ddf3..0bdb06e3a 100644 --- a/java/res/values-es/strings.xml +++ b/java/res/values-es/strings.xml @@ -87,7 +87,7 @@ <string name="spoken_description_shift" msgid="244197883292549308">"Mayús"</string> <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Mayúsculas activadas (tocar para inhabilitar)"</string> <string name="spoken_description_caps_lock" msgid="3276478269526304432">"Bloqueo de mayúsculas activado (tocar para inhabilitar)"</string> - <string name="spoken_description_delete" msgid="8740376944276199801">"Suprimir"</string> + <string name="spoken_description_delete" msgid="8740376944276199801">"Eliminar"</string> <string name="spoken_description_to_symbol" msgid="5486340107500448969">"Símbolos"</string> <string name="spoken_description_to_alpha" msgid="23129338819771807">"Letras"</string> <string name="spoken_description_to_numeric" msgid="591752092685161732">"Números"</string> diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml new file mode 100644 index 000000000..7630cdeec --- /dev/null +++ b/java/res/values-ka/strings.xml @@ -0,0 +1,241 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/* +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="english_ime_input_options" msgid="3909945612939668554">"შეყვანის მეთოდები"</string> + <string name="english_ime_research_log" msgid="8492602295696577851">"კვლევის აღრიცხვის ბრძანებები"</string> + <string name="use_contacts_for_spellchecking_option_title" msgid="5374120998125353898">"კონტაქტების სახელების მოძიება"</string> + <string name="use_contacts_for_spellchecking_option_summary" msgid="8754413382543307713">"მართლწერის შემმოწმებელი ერთეულებს თქვენი კონტაქტების სიიდან იყენებს"</string> + <string name="vibrate_on_keypress" msgid="5258079494276955460">"ვიბრაცია კლავიშზე დაჭერისას"</string> + <string name="sound_on_keypress" msgid="6093592297198243644">"ხმაური კლავიშზე დაჭერისას"</string> + <string name="popup_on_keypress" msgid="123894815723512944">"გადიდება ღილაკზე დაჭერისას"</string> + <string name="general_category" msgid="1859088467017573195">"ზოგადი"</string> + <string name="correction_category" msgid="2236750915056607613">"ტექსტის კორექცია"</string> + <string name="gesture_typing_category" msgid="497263612130532630">"ჟესტებით წერა"</string> + <string name="misc_category" msgid="6894192814868233453">"სხვა პარამეტრები"</string> + <string name="advanced_settings" msgid="362895144495591463">"გაფართუებული პარამეტრები"</string> + <string name="advanced_settings_summary" msgid="4487980456152830271">"პარამეტრები ექსპერტთათვის"</string> + <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"შეყვანის სხვა მეთოდებზე გადართვა"</string> + <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"ენის გადართვის ღილაკს შეყვანის სხვა მეთოდებსაც შეიცავს"</string> + <string name="show_language_switch_key" msgid="5915478828318774384">"ენის გადართვის კლავიში"</string> + <string name="show_language_switch_key_summary" msgid="7343403647474265713">"აჩვენე, როდესაც ჩართულია სხვადასხვა შეყვანის ენა"</string> + <string name="sliding_key_input_preview" msgid="6604262359510068370">"გასრიალების ინდიკატ. ჩვენება"</string> + <string name="sliding_key_input_preview_summary" msgid="6340524345729093886">"Shift ან Symbol კლავიშებიდან გასრიალებისას ვიზუალური მინიშნების ჩვენება"</string> + <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"ამომხტ.კლავიშის დაყოვნება"</string> + <string name="key_preview_popup_dismiss_no_delay" msgid="2096123151571458064">"არ დაყოვნდეს"</string> + <string name="key_preview_popup_dismiss_default_delay" msgid="2166964333903906734">"ნაგულისხმევი"</string> + <string name="abbreviation_unit_milliseconds" msgid="8700286094028323363">"<xliff:g id="MILLISECONDS">%s</xliff:g>მწმ"</string> + <string name="use_contacts_dict" msgid="4435317977804180815">"კონტაქტის სახელების შეთავაზება"</string> + <string name="use_contacts_dict_summary" msgid="6599983334507879959">"კონტაქტებიდან სახელების გამოყენება შეთავაზებებისთვის და კორექციისთვის"</string> + <string name="use_double_space_period" msgid="8781529969425082860">"წერტილი ორმაგი შორისით"</string> + <string name="use_double_space_period_summary" msgid="6532892187247952799">"შორისზე ორჯერ შეხება დაწერს წერტილს და შორისის სიმბოლოს"</string> + <string name="auto_cap" msgid="1719746674854628252">"ავტო-კაპიტალიზაცია"</string> + <string name="auto_cap_summary" msgid="7934452761022946874">"ყოველი წინადადების პირველი სიტყვის კაპიტალიზაცია"</string> + <string name="edit_personal_dictionary" msgid="3996910038952940420">"პერსონალური ლექსიკონი"</string> + <string name="configure_dictionaries_title" msgid="4238652338556902049">"დამატებითი ლექსიკონები"</string> + <string name="main_dictionary" msgid="4798763781818361168">"მთავარი ლექსიკონი"</string> + <string name="prefs_show_suggestions" msgid="8026799663445531637">"კორექციის შეთავაზებების ჩვენება"</string> + <string name="prefs_show_suggestions_summary" msgid="1583132279498502825">"წერისას შეთავაზებული სიტყვების ჩვენება"</string> + <string name="prefs_suggestion_visibility_show_name" msgid="3219916594067551303">"ყოველთვის ჩვენება"</string> + <string name="prefs_suggestion_visibility_show_only_portrait_name" msgid="3859783767435239118">"პორტრეტის რეჟიმში ჩვენება"</string> + <string name="prefs_suggestion_visibility_hide_name" msgid="6309143926422234673">"ყოველთვის დამალვა"</string> + <string name="prefs_block_potentially_offensive_title" msgid="5078480071057408934">"შეურაცხმყოფელი სიტყვების დაბლოკვა"</string> + <string name="prefs_block_potentially_offensive_summary" msgid="2371835479734991364">"არ მოხდეს პოტენციურად შეურაცხმყოფელი სიტყვების შეთავაზება"</string> + <string name="auto_correction" msgid="7630720885194996950">"ავტოკორექცია"</string> + <string name="auto_correction_summary" msgid="5625751551134658006">"შორისი და პუნქტუაცია ავტომატურად ასწორებს არასწორად აკრეფილ სიტყვებს"</string> + <string name="auto_correction_threshold_mode_off" msgid="8470882665417944026">"გამორთულია"</string> + <string name="auto_correction_threshold_mode_modest" msgid="8788366690620799097">"მოკრძალებული"</string> + <string name="auto_correction_threshold_mode_aggeressive" msgid="3524029103734923819">"აგრესიული"</string> + <string name="auto_correction_threshold_mode_very_aggeressive" msgid="3386782235540547678">"ძალიან აგრესიული"</string> + <string name="bigram_prediction" msgid="1084449187723948550">"შემდეგი სიტყვის შეთავაზებები"</string> + <string name="bigram_prediction_summary" msgid="3896362682751109677">"შეთავაზებებისას წინა სიტყვის გამოყენება"</string> + <string name="gesture_input" msgid="826951152254563827">"ჟესტებით წერის ჩართვა"</string> + <string name="gesture_input_summary" msgid="9180350639305731231">"სიტყვის შეყვანა ასო-ნიშებზე გასრიალებით"</string> + <string name="gesture_preview_trail" msgid="3802333369335722221">"ჟესტიკულაციის კუდის ჩვენება"</string> + <string name="gesture_floating_preview_text" msgid="4443240334739381053">"დინამიურად მოლივლივე გადახედვა"</string> + <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"ჟესტიკულაციისას შეთავაზებული სიტყვის ნახვა"</string> + <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : შეინახა"</string> + <string name="label_go_key" msgid="1635148082137219148">"წასვლ"</string> + <string name="label_next_key" msgid="362972844525672568">"შემდ."</string> + <string name="label_previous_key" msgid="1211868118071386787">"წინა"</string> + <string name="label_done_key" msgid="2441578748772529288">"დასრულდა"</string> + <string name="label_send_key" msgid="2815056534433717444">"გაგზ."</string> + <string name="label_pause_key" msgid="181098308428035340">"პაუზა"</string> + <string name="label_wait_key" msgid="6402152600878093134">"მოცდა"</string> + <string name="spoken_use_headphones" msgid="896961781287283493">"შეაერთეთ ყურსაცვამი, რათა მოისმინოთ აკრეფილი პაროლის კლავიშების სახელები."</string> + <string name="spoken_current_text_is" msgid="2485723011272583845">"მიმდინარე ტექსტი არის %s"</string> + <string name="spoken_no_text_entered" msgid="7479685225597344496">"ტექსტ არ შეყვანილა"</string> + <string name="spoken_description_unknown" msgid="3197434010402179157">"კლავიატურის კოდი %d"</string> + <string name="spoken_description_shift" msgid="244197883292549308">"Shift-"</string> + <string name="spoken_description_shift_shifted" msgid="1681877323344195035">"Shift ჩართულია (შეეხეთ გამოსართავად)"</string> + <string name="spoken_description_caps_lock" msgid="3276478269526304432">"ჩართულია Caps (შეეხეთ გამოსართავად)"</string> + <string name="spoken_description_delete" msgid="8740376944276199801">"Delete"</string> + <string name="spoken_description_to_symbol" msgid="5486340107500448969">"სიმბოლოები"</string> + <string name="spoken_description_to_alpha" msgid="23129338819771807">"ასოები"</string> + <string name="spoken_description_to_numeric" msgid="591752092685161732">"ნომრები"</string> + <string name="spoken_description_settings" msgid="4627462689603838099">"პარამეტრები"</string> + <string name="spoken_description_tab" msgid="2667716002663482248">"Tab"</string> + <string name="spoken_description_space" msgid="2582521050049860859">"შორისი"</string> + <string name="spoken_description_mic" msgid="615536748882611950">"ხმოვანი შეყვანა"</string> + <string name="spoken_description_smiley" msgid="2256309826200113918">"მომღიმარი სახე"</string> + <string name="spoken_description_return" msgid="8178083177238315647">"Return"</string> + <string name="spoken_description_search" msgid="1247236163755920808">"ძიება"</string> + <string name="spoken_description_dot" msgid="40711082435231673">"წერტილი"</string> + <string name="spoken_description_language_switch" msgid="5507091328222331316">"ენის გადართვა"</string> + <string name="spoken_description_action_next" msgid="8636078276664150324">"შემდეგი"</string> + <string name="spoken_description_action_previous" msgid="800872415009336208">"წინა"</string> + <string name="spoken_description_shiftmode_on" msgid="5700440798609574589">"Shift ჩართულია"</string> + <string name="spoken_description_shiftmode_locked" msgid="593175803181701830">"Caps lock ჩართულია"</string> + <string name="spoken_description_shiftmode_off" msgid="657219998449174808">"Shift გამორთულია"</string> + <string name="spoken_description_mode_symbol" msgid="7183343879909747642">"სიმბოლოების რეჟიმი"</string> + <string name="spoken_description_mode_alpha" msgid="3528307674390156956">"ასოების რეჟიმი"</string> + <string name="spoken_description_mode_phone" msgid="6520207943132026264">"ტელეფონის რეჟიმი"</string> + <string name="spoken_description_mode_phone_shift" msgid="5499629753962641227">"ტელეფონის სიმბოლოების რეჟიმი"</string> + <string name="announce_keyboard_hidden" msgid="8718927835531429807">"კლავიატურა დამალულია"</string> + <string name="announce_keyboard_mode" msgid="4729081055438508321">"ნაჩვენებია <xliff:g id="MODE">%s</xliff:g> კლავიატურა"</string> + <string name="keyboard_mode_date" msgid="3137520166817128102">"თარიღი"</string> + <string name="keyboard_mode_date_time" msgid="339593358488851072">"თარიღი და დრო"</string> + <string name="keyboard_mode_email" msgid="6216248078128294262">"ელფოსტა"</string> + <string name="keyboard_mode_im" msgid="1137405089766557048">"შეტყობინებები"</string> + <string name="keyboard_mode_number" msgid="7991623440699957069">"რიცხვები"</string> + <string name="keyboard_mode_phone" msgid="6851627527401433229">"ტელეფონი"</string> + <string name="keyboard_mode_text" msgid="6479436687899701619">"ტექსტი"</string> + <string name="keyboard_mode_time" msgid="4381856885582143277">"დრო"</string> + <string name="keyboard_mode_url" msgid="1519819835514911218">"URL"</string> + <string name="voice_input" msgid="3583258583521397548">"ხმოვანი შეყვანის კლავიში"</string> + <string name="voice_input_modes_main_keyboard" msgid="3360660341121083174">"მთავარ კლავიატურაზე"</string> + <string name="voice_input_modes_symbols_keyboard" msgid="7203213240786084067">"სიმბოლოთა კლავიატურაზე"</string> + <string name="voice_input_modes_off" msgid="3745699748218082014">"გამორთულია"</string> + <string name="voice_input_modes_summary_main_keyboard" msgid="6586544292900314339">"მიკროფონი მთავარ კლავიატურაზე"</string> + <string name="voice_input_modes_summary_symbols_keyboard" msgid="5233725927281932391">"მიკროფონი სიმბოლოთა კლავიატურაზე"</string> + <string name="voice_input_modes_summary_off" msgid="63875609591897607">"ხმოვანი შეყვანა გამორთულია"</string> + <string name="configure_input_method" msgid="373356270290742459">"შეყვანის მეთოდების კონფიგურაცია"</string> + <string name="language_selection_title" msgid="1651299598555326750">"შეყვანის ენები"</string> + <string name="send_feedback" msgid="1780431884109392046">"უკუკავშირი"</string> + <string name="select_language" msgid="3693815588777926848">"შეყვანის ენები"</string> + <string name="hint_add_to_dictionary" msgid="573678656946085380">"შეეხეთ ისევ შესანახად"</string> + <string name="has_dictionary" msgid="6071847973466625007">"ხელმისაწვდომია ლექსიკონი"</string> + <string name="prefs_enable_log" msgid="6620424505072963557">"მომხმარებლის უკუკავშირის ჩართვა"</string> + <string name="prefs_description_log" msgid="7525225584555429211">"შეიტანეთ წვლილი შეყვანის ამ მეთოდის გაუმჯობესებაში და გააგზავნეთ მოხმარების სტატისტიკა ავარიული გათიშვების ანგარიშები"</string> + <string name="keyboard_layout" msgid="8451164783510487501">"კლავიატურის თემა"</string> + <string name="subtype_en_GB" msgid="88170601942311355">"ინგლისური (გართ. სამ.)"</string> + <string name="subtype_en_US" msgid="6160452336634534239">"ინგლისური (აშშ)"</string> + <string name="subtype_es_US" msgid="5583145191430180200">"ესპანური (აშშ)"</string> + <string name="subtype_with_layout_en_GB" msgid="2179097748724725906">"ინგლისური (გაერთ. სამ.) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string> + <string name="subtype_with_layout_en_US" msgid="1362581347576714579">"ინგლისური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string> + <string name="subtype_with_layout_es_US" msgid="6261791057007890189">"ესპანური (აშშ) (<xliff:g id="LAYOUT">%s</xliff:g>)"</string> + <string name="subtype_no_language" msgid="141420857808801746">"ენის გარეშე"</string> + <string name="subtype_no_language_qwerty" msgid="2956121451616633133">"ენის გარეშე (QWERTY)"</string> + <string name="subtype_no_language_qwertz" msgid="1177848172397202890">"ენის გარეშე (QWERTZ)"</string> + <string name="subtype_no_language_azerty" msgid="8721460968141187394">"ენის გარეშე (AZERTY)"</string> + <string name="subtype_no_language_dvorak" msgid="3122976737669823935">"ენის გარეშე (Dvorak)"</string> + <string name="subtype_no_language_colemak" msgid="4205992994906097244">"ენის გარეშე (Colemak)"</string> + <string name="subtype_no_language_pcqwerty" msgid="8840928374394180189">"ენის გარეშე (PC)"</string> + <string name="custom_input_styles_title" msgid="8429952441821251512">"შეყვანის სტილების კონფიგურაცია"</string> + <string name="add_style" msgid="6163126614514489951">"სტილის დამატება"</string> + <string name="add" msgid="8299699805688017798">"დამატება"</string> + <string name="remove" msgid="4486081658752944606">"ამოშლა"</string> + <string name="save" msgid="7646738597196767214">"შენახვა"</string> + <string name="subtype_locale" msgid="8576443440738143764">"ენა"</string> + <string name="keyboard_layout_set" msgid="4309233698194565609">"განლაგება"</string> + <string name="custom_input_style_note_message" msgid="8826731320846363423">"თქვენი მორგებული შეყვანის სტილი გამოყენებამდე უნდა გაააქტიუროთ. გსურთ მისი აქტივაცია ახლა?"</string> + <string name="enable" msgid="5031294444630523247">"ჩართვა"</string> + <string name="not_now" msgid="6172462888202790482">"ახლა არა"</string> + <string name="custom_input_style_already_exists" msgid="8008728952215449707">"შეყვანის იგივე სტილი უკე არსებობს: <xliff:g id="INPUT_STYLE_NAME">%s</xliff:g>"</string> + <string name="prefs_usability_study_mode" msgid="1261130555134595254">"გამოყენებადობის კვლევის რეჟიმი"</string> + <string name="prefs_key_longpress_timeout_settings" msgid="6102240298932897873">"კლავიშზე გრძელი დაჭერის დაყოვნება"</string> + <string name="prefs_keypress_vibration_duration_settings" msgid="7918341459947439226">"კლავიშზე დაჭერის ვიბრაციის ხანგრძლივობა"</string> + <string name="prefs_keypress_sound_volume_settings" msgid="6027007337036891623">"კლავიშზე დაჭერის ხმა"</string> + <string name="prefs_read_external_dictionary" msgid="2588931418575013067">"გარე ლექსიკონის ფაილის წაკითხვა"</string> + <string name="read_external_dictionary_no_files_message" msgid="4947420942224623792">"ჩამოტვირთვების საქაღალდეში ლექსიკონის ფაილები არ არის"</string> + <string name="read_external_dictionary_multiple_files_title" msgid="7637749044265808628">"ინსტალაციისათვის აირჩიეთ ლექსიკონის ფაილი"</string> + <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ნამდვილად გსურთ ამ ფაილის <xliff:g id="LOCALE_NAME">%s</xliff:g>-ისთვის ინსტალაცია?"</string> + <string name="error" msgid="8940763624668513648">"წარმოიშვა შეცდომა"</string> + <string name="button_default" msgid="3988017840431881491">"ნაგულისხმევი"</string> + <string name="setup_welcome_title" msgid="6112821709832031715">"კეთილი იყოს თქვენი მობრძანება <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ში"</string> + <string name="setup_welcome_additional_description" msgid="8150252008545768953">"ჟესტებით წერით"</string> + <string name="setup_start_action" msgid="8936036460897347708">"დაწყება"</string> + <string name="setup_next_action" msgid="371821437915144603">"შემდეგი საფეხური"</string> + <string name="setup_steps_title" msgid="6400373034871816182">"მიმდინარეობს <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის დაყენება"</string> + <string name="setup_step1_title" msgid="3147967630253462315">"<xliff:g id="APPLICATION_NAME">%s</xliff:g>-ის ჩართვა"</string> + <string name="setup_step1_instruction" msgid="2578631936624637241">"გთხოვთ მონიშნოთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენი ენის და შეყვანის პარამეტრებში. ეს უფლებას მიცემს მას გაეშვას თქვენს მოწყობილობაზე."</string> + <string name="setup_step1_finished_instruction" msgid="10761482004957994">"<xliff:g id="APPLICATION_NAME">%s</xliff:g> უკვე გააქტიურებულია თქვენი ენის შეყვანის პარამეტრებში, ასე რომ ეს საფეხური დასრულებულია. გადადით შემდეგ საფეხურზე!"</string> + <string name="setup_step1_action" msgid="4366513534999901728">"პარამეტრებში გააქტიურება"</string> + <string name="setup_step2_title" msgid="6860725447906690594">"გადართეთ <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ზე"</string> + <string name="setup_step2_instruction" msgid="9141481964870023336">"შემდეგ, აირჩიეთ „<xliff:g id="APPLICATION_NAME">%s</xliff:g>“ თქვენს აქტიურ შეყვანის მეთოდად."</string> + <string name="setup_step2_action" msgid="1660330307159824337">"შეყვანის მეთოდების გადართვა"</string> + <string name="setup_step3_title" msgid="3154757183631490281">"გილოცავთ, პრცესი დასრულდა!"</string> + <string name="setup_step3_instruction" msgid="8025981829605426000">"ამიერიდან შეძლებთ ყველა სასურველ აპში <xliff:g id="APPLICATION_NAME">%s</xliff:g>-ით წერას."</string> + <string name="setup_step3_action" msgid="600879797256942259">"დამატებითი ენების კონფიგურაცია"</string> + <string name="setup_finish_action" msgid="276559243409465389">"დასრულებული"</string> + <string name="show_setup_wizard_icon" msgid="5008028590593710830">"აპის ხატულის ჩვენება"</string> + <string name="show_setup_wizard_icon_summary" msgid="4119998322536880213">"გაშვების ხატულის ჩვენება გამშვებში"</string> + <string name="app_name" msgid="6320102637491234792">"ლექსიკონის პროვაიდერი"</string> + <string name="dictionary_provider_name" msgid="3027315045397363079">"ლექსიკონის პროვაიდერი"</string> + <string name="dictionary_service_name" msgid="6237472350693511448">"ლექსიკონს სერვისი"</string> + <string name="download_description" msgid="6014835283119198591">"ლექსიკონის განახლების მონაცემები"</string> + <string name="dictionary_settings_title" msgid="8091417676045693313">"დამატებითი ლექსიკონები"</string> + <string name="dictionary_install_over_metered_network_prompt" msgid="3587517870006332980">"ხელმისაწვდომია ლექსიკონი"</string> + <string name="dictionary_settings_summary" msgid="5305694987799824349">"ლექსიკონების პარამეტრები"</string> + <string name="user_dictionaries" msgid="3582332055892252845">"მომხმარებლის ლექსიკონები"</string> + <string name="default_user_dict_pref_name" msgid="1625055720489280530">"მომხარებლის ლექსიკონი"</string> + <string name="dictionary_available" msgid="4728975345815214218">"ხელმისაწვდომია ლექსიკონი"</string> + <string name="dictionary_downloading" msgid="2982650524622620983">"მიმდინარეობს ჩამოტვირთვა"</string> + <string name="dictionary_installed" msgid="8081558343559342962">"დაყენებულია"</string> + <string name="dictionary_disabled" msgid="8950383219564621762">"დაყენებულია, გაუქმებულია"</string> + <string name="cannot_connect_to_dict_service" msgid="9216933695765732398">"ლექსიკონის სერვისთან დაკავშირებით პრობლემა წარმოიშვა"</string> + <string name="no_dictionaries_available" msgid="8039920716566132611">"ლექსიკონები მიუწვდომელია"</string> + <string name="check_for_updates_now" msgid="8087688440916388581">"განახლება"</string> + <string name="last_update" msgid="730467549913588780">"ბოლო განახლება"</string> + <string name="message_updating" msgid="4457761393932375219">"მიმდინარეობს განახლებების შემოწმება"</string> + <string name="message_loading" msgid="8689096636874758814">"იტვირთება..."</string> + <string name="main_dict_description" msgid="3072821352793492143">"მთავარი ლექსიკონი"</string> + <string name="cancel" msgid="6830980399865683324">"გაუქმება"</string> + <string name="install_dict" msgid="180852772562189365">"ინსტალაცია"</string> + <string name="cancel_download_dict" msgid="7843340278507019303">"გაუქმება"</string> + <string name="delete_dict" msgid="756853268088330054">"წაშლა"</string> + <string name="should_download_over_metered_prompt" msgid="2878629598667658845">"თქვენს მიერ მობილურ მოწყობილობაზე არჩეული ენისათვის ხელმისაწვდომია ლექსიკონი.<br/> გირჩევთ, <b>ჩამოტვირთოთ</b> <xliff:g id="LANGUAGE">%1$s</xliff:g> ლექსიკონი, რათა გაიმარტივოთ ტექსტის შეყვანა.<br/> <br/> ჩამოტვირთვას შესაძლოა დაჭირდეს ერთი ან ორი წუთი 3G სისწრაფეზე. თუ ულიმიტო არ გაგაჩნიათ <b> მობილური ინტერნეტის ტარიფი</b>.<br/&gt, შესაძლოა ჩამოტვირთვა დამატებით გადასახადებთან იყოს დაკავშირებული; თუ არ ხართ დარწმუნებული მობილური ინტერნეტის აქტიური ტარიფის შესახებ, გირჩევთ იპოვოთ Wi-Fi კავშირი და ავტომატურად დაიწყოთ ჩამოტვირთვა.<br/> <br/> რჩევა: ლექსიკონების ჩამოტვირთვა და ამოშლა შესაძლებელია სექციიდან <b>ენა და შეყვანა</b> სექციიდან, თქვენი მობილური მოწყობილობის <b>პარამეტრების</b> მენიუში."</string> + <string name="download_over_metered" msgid="1643065851159409546">"ახლა ჩამოტვირთვა (<xliff:g id="SIZE_IN_MEGABYTES">%1$.1f</xliff:g>მბაიტი)"</string> + <string name="do_not_download_over_metered" msgid="2176209579313941583">"Wi-Fi კავშირზე ჩამოტვირთვა"</string> + <string name="dict_available_notification_title" msgid="6514288591959117288">"<xliff:g id="LANGUAGE">%1$s</xliff:g>-ისთვის ხელმისაწვდომია ლექსიკონი"</string> + <string name="dict_available_notification_description" msgid="1075194169443163487">"დააჭირეთ განხილვას და ჩამოტვირთეთ"</string> + <string name="toast_downloading_suggestions" msgid="1313027353588566660">"ჩამოტვირთვა: <xliff:g id="LANGUAGE">%1$s</xliff:g>-ის შემოთავაზებები მალე მომზადდება."</string> + <string name="version_text" msgid="2715354215568469385">"ვერსია <xliff:g id="VERSION_NUMBER">%1$s</xliff:g>"</string> + <string name="user_dict_settings_add_menu_title" msgid="1254195365689387076">"დამატება"</string> + <string name="user_dict_settings_add_dialog_title" msgid="4096700390211748168">"ლექსიკონში დამატება"</string> + <string name="user_dict_settings_add_screen_title" msgid="5818914331629278758">"ფრაზა"</string> + <string name="user_dict_settings_add_dialog_more_options" msgid="5671682004887093112">"მეტი ვარიანტები"</string> + <string name="user_dict_settings_add_dialog_less_options" msgid="2716586567241724126">"ნაკლები ვარიანტები"</string> + <string name="user_dict_settings_add_dialog_confirm" msgid="4703129507388332950">"კარგი"</string> + <string name="user_dict_settings_add_word_option_name" msgid="6665558053408962865">"სიტყვა:"</string> + <string name="user_dict_settings_add_shortcut_option_name" msgid="3094731590655523777">"მალსახმობი:"</string> + <string name="user_dict_settings_add_locale_option_name" msgid="4738643440987277705">"ენა:"</string> + <string name="user_dict_settings_add_word_hint" msgid="4902434148985906707">"შიყვანეთ სიტყვა"</string> + <string name="user_dict_settings_add_shortcut_hint" msgid="2265453012555060178">"არასავალდებულო მალსახმობი"</string> + <string name="user_dict_settings_edit_dialog_title" msgid="3765774633869590352">"სიტყვის შესწორება"</string> + <string name="user_dict_settings_context_menu_edit_title" msgid="6812255903472456302">"რედაქტირება"</string> + <string name="user_dict_settings_context_menu_delete_title" msgid="8142932447689461181">"წაშლა"</string> + <string name="user_dict_settings_empty_text" msgid="558499587532668203">"თქვენ არ გაქვთ სიტყვები მომხმარებლის ლექსიკონში. დაამატეთ სიტყვები ღილაკ Add (+) შეხებით."</string> + <string name="user_dict_settings_all_languages" msgid="8276126583216298886">"ყველა ენისთვის"</string> + <string name="user_dict_settings_more_languages" msgid="7131268499685180461">"სხვა ენები…"</string> + <string name="user_dict_settings_delete" msgid="110413335187193859">"წაშლა"</string> + <string name="user_dict_fast_scroll_alphabet" msgid="5431919401558285473">" ABCDEFGHIJKLMNOPQRSTUVWXYZ"</string> +</resources> diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml index 53e46aae0..116ce1644 100644 --- a/java/res/values-sw/strings.xml +++ b/java/res/values-sw/strings.xml @@ -31,7 +31,7 @@ <string name="correction_category" msgid="2236750915056607613">"Marekebisho ya maandishi"</string> <string name="gesture_typing_category" msgid="497263612130532630">"Kuandika kwa ishara"</string> <string name="misc_category" msgid="6894192814868233453">"Chaguo zingine"</string> - <string name="advanced_settings" msgid="362895144495591463">"Mipangilio mahiri"</string> + <string name="advanced_settings" msgid="362895144495591463">"Mipangilio ya kina"</string> <string name="advanced_settings_summary" msgid="4487980456152830271">"Chaguo za wataalamu"</string> <string name="include_other_imes_in_language_switch_list" msgid="4533689960308565519">"Badilisha hadi kwa mbinu zingine za ingizo"</string> <string name="include_other_imes_in_language_switch_list_summary" msgid="840637129103317635">"Ufunguo wa kubadilisha lugha unashughulikia mbinu zingine za ingizo pia"</string> diff --git a/java/res/values/keypress-vibration-durations.xml b/java/res/values/keypress-vibration-durations.xml index 0474b1c92..e8d92256b 100644 --- a/java/res/values/keypress-vibration-durations.xml +++ b/java/res/values/keypress-vibration-durations.xml @@ -51,7 +51,7 @@ <!-- HTC One X --> <item>MODEL=HTC One X:MANUFACTURER=HTC,20</item> <!-- HTC One --> - <item>MODEL=HTC One:MANUFACTURER=HTC,15</item> + <item>MODEL=HTC One( special edition)?:MANUFACTURER=HTC,15</item> <!-- Motorola Razor M --> <item>MODEL=XT907:MANUFACTURER=motorola,30</item> <!-- Sony Xperia Z --> diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java index 69615887f..df0e3f0e1 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java @@ -28,13 +28,13 @@ public class DictionaryPackConstants { * The root domain for the dictionary pack, upon which authorities and actions will append * their own distinctive strings. */ - private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack"; + private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack.aosp"; /** * Authority for the ContentProvider protocol. */ // TODO: find some way to factorize this string with the one in the resources - public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp"; + public static final String AUTHORITY = DICTIONARY_DOMAIN; /** * The action of the intent for publishing that new dictionary data is available. @@ -52,7 +52,14 @@ public class DictionaryPackConstants { */ public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN + ".UNKNOWN_CLIENT"; + // In the above intents, the name of the string extra that contains the name of the client // we want information about. public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client"; + + /** + * The action of the intent to tell the dictionary provider to update now. + */ + public static final String UPDATE_NOW_INTENT_ACTION = DICTIONARY_DOMAIN + + ".UPDATE_NOW"; } diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java index 46bb5543a..6e3dd7109 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java @@ -54,12 +54,7 @@ public final class DictionaryService extends Service { /** * The package name, to use in the intent actions. */ - private static final String PACKAGE_NAME = "com.android.android.inputmethod.latin"; - - /** - * The action of the intent to tell the dictionary provider to update now. - */ - private static final String UPDATE_NOW_INTENT_ACTION = PACKAGE_NAME + ".UPDATE_NOW"; + private static final String PACKAGE_NAME = "com.android.inputmethod.latin"; /** * The action of the date changing, used to schedule a periodic freshness check @@ -173,7 +168,7 @@ public final class DictionaryService extends Service { // at midnight local time, but it may happen if the user changes the date // by hand or something similar happens. checkTimeAndMaybeSetupUpdateAlarm(context); - } else if (UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { + } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) { // Intent to trigger an update now. UpdateHandler.update(context, false); } else { @@ -196,7 +191,7 @@ public final class DictionaryService extends Service { // It doesn't matter too much if this is very inexact. final long now = System.currentTimeMillis(); final long alarmTime = now + new Random().nextInt(MAX_ALARM_DELAY); - final Intent updateIntent = new Intent(DictionaryService.UPDATE_NOW_INTENT_ACTION); + final Intent updateIntent = new Intent(DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION); final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, updateIntent, PendingIntent.FLAG_CANCEL_CURRENT); diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java index 1511dbcfe..dac12137d 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java @@ -36,8 +36,6 @@ import java.util.TreeMap; * Various helper functions for the state database */ public class MetadataDbHelper extends SQLiteOpenHelper { - - @SuppressWarnings("unused") private static final String TAG = MetadataDbHelper.class.getSimpleName(); // This was the initial release version of the database. It should never be @@ -437,37 +435,37 @@ public class MetadataDbHelper extends SQLiteOpenHelper { */ public static ContentValues completeWithDefaultValues(final ContentValues result) throws BadFormatException { - if (!result.containsKey(WORDLISTID_COLUMN) || !result.containsKey(LOCALE_COLUMN)) { + if (null == result.get(WORDLISTID_COLUMN) || null == result.get(LOCALE_COLUMN)) { throw new BadFormatException(); } // 0 for the pending id, because there is none - if (!result.containsKey(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0); + if (null == result.get(PENDINGID_COLUMN)) result.put(PENDINGID_COLUMN, 0); // This is a binary blob of a dictionary - if (!result.containsKey(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK); + if (null == result.get(TYPE_COLUMN)) result.put(TYPE_COLUMN, TYPE_BULK); // This word list is unknown, but it's present, else we wouldn't be here, so INSTALLED - if (!result.containsKey(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED); + if (null == result.get(STATUS_COLUMN)) result.put(STATUS_COLUMN, STATUS_INSTALLED); // No description unless specified, because we can't guess it - if (!result.containsKey(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, ""); + if (null == result.get(DESCRIPTION_COLUMN)) result.put(DESCRIPTION_COLUMN, ""); // File name - this is an asset, so it works as an already deleted file. // hence, we need to supply a non-existent file name. Anything will // do as long as it returns false when tested with File#exist(), and // the empty string does not, so it's set to "_". - if (!result.containsKey(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_"); + if (null == result.get(LOCAL_FILENAME_COLUMN)) result.put(LOCAL_FILENAME_COLUMN, "_"); // No remote file name : this can't be downloaded. Unless specified. - if (!result.containsKey(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, ""); + if (null == result.get(REMOTE_FILENAME_COLUMN)) result.put(REMOTE_FILENAME_COLUMN, ""); // 0 for the update date : 1970/1/1. Unless specified. - if (!result.containsKey(DATE_COLUMN)) result.put(DATE_COLUMN, 0); + if (null == result.get(DATE_COLUMN)) result.put(DATE_COLUMN, 0); // Checksum unknown unless specified - if (!result.containsKey(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, ""); + if (null == result.get(CHECKSUM_COLUMN)) result.put(CHECKSUM_COLUMN, ""); // No filesize unless specified - if (!result.containsKey(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0); + if (null == result.get(FILESIZE_COLUMN)) result.put(FILESIZE_COLUMN, 0); // Smallest possible version unless specified - if (!result.containsKey(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1); + if (null == result.get(VERSION_COLUMN)) result.put(VERSION_COLUMN, 1); // Assume current format unless specified - if (!result.containsKey(FORMATVERSION_COLUMN)) + if (null == result.get(FORMATVERSION_COLUMN)) result.put(FORMATVERSION_COLUMN, UpdateHandler.MAXIMUM_SUPPORTED_FORMAT_VERSION); // No flags unless specified - if (!result.containsKey(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0); + if (null == result.get(FLAGS_COLUMN)) result.put(FLAGS_COLUMN, 0); return result; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index d4051f74b..a074b6334 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -80,6 +80,14 @@ public final class KeyboardLayoutSet { private final Context mContext; private final Params mParams; + // How many layouts we forcibly keep in cache. This only includes ALPHABET (default) and + // ALPHABET_AUTOMATIC_SHIFTED layouts - other layouts may stay in memory in the map of + // soft-references, but we forcibly cache this many alphabetic/auto-shifted layouts. + private static final int FORCIBLE_CACHE_SIZE = 4; + // By construction of soft references, anything that is also referenced somewhere else + // will stay in the cache. So we forcibly keep some references in an array to prevent + // them from disappearing from sKeyboardCache. + private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE]; private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache = CollectionUtils.newHashMap(); private static final KeysCache sKeysCache = new KeysCache(); @@ -110,6 +118,7 @@ public final class KeyboardLayoutSet { boolean mNoSettingsKey; boolean mLanguageSwitchKeyEnabled; InputMethodSubtype mSubtype; + boolean mIsSpellChecker; int mOrientation; int mKeyboardWidth; int mKeyboardHeight; @@ -185,7 +194,18 @@ public final class KeyboardLayoutSet { elementParams.mProximityCharsCorrectionEnabled); keyboard = builder.build(); sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard)); - + if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET + || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) + && !mParams.mIsSpellChecker) { + // We only forcibly cache the primary, "ALPHABET", layouts. + for (int i = sForcibleKeyboardCache.length - 1; i >= 1; --i) { + sForcibleKeyboardCache[i] = sForcibleKeyboardCache[i - 1]; + } + sForcibleKeyboardCache[0] = keyboard; + if (DEBUG_CACHE) { + Log.d(TAG, "forcing caching of keyboard with id=" + id); + } + } if (DEBUG_CACHE) { Log.d(TAG, "keyboard cache size=" + sKeyboardCache.size() + ": " + ((ref == null) ? "LOAD" : "GCed") + " id=" + id); @@ -272,6 +292,11 @@ public final class KeyboardLayoutSet { return this; } + public Builder setIsSpellChecker(final boolean isSpellChecker) { + mParams.mIsSpellChecker = true; + return this; + } + public Builder setOptions(final boolean voiceKeyEnabled, final boolean voiceKeyOnMain, final boolean languageSwitchKeyEnabled) { @SuppressWarnings("deprecation") @@ -422,7 +447,8 @@ public final class KeyboardLayoutSet { final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype(locale, layout, null); return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, - SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false); + SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false /* testCasesHaveTouchCoordinates */, + true /* isSpellChecker */); } @UsedForTesting @@ -442,18 +468,20 @@ public final class KeyboardLayoutSet { throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or " + "ORIENTATION_PORTRAIT: orientation=" + orientation); } - return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates); + return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates, + false /* isSpellChecker */); } private static KeyboardLayoutSet createKeyboardSet(final Context context, final InputMethodSubtype subtype, final int width, final int height, - final boolean testCasesHaveTouchCoordinates) { + final boolean testCasesHaveTouchCoordinates, final boolean isSpellChecker) { final EditorInfo editorInfo = new EditorInfo(); editorInfo.inputType = InputType.TYPE_CLASS_TEXT; final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( context, editorInfo); builder.setScreenGeometry(width, height); builder.setSubtype(subtype); + builder.setIsSpellChecker(isSpellChecker); if (!testCasesHaveTouchCoordinates) { // For spell checker and tests builder.disableTouchPositionCorrectionData(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java index 0f3cd7887..fb69e22e7 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java @@ -245,7 +245,7 @@ final class GestureTrail { final float body1 = r1 * params.mTrailBodyRatio; final float body2 = r2 * params.mTrailBodyRatio; final Path path = roundedLine.makePath(p1x, p1y, body1, p2x, p2y, body2); - if (path != null) { + if (!path.isEmpty()) { roundedLine.getBounds(mRoundedLineBounds); if (params.mTrailShadowEnabled) { final float shadow2 = r2 * params.mTrailShadowRatio; diff --git a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java index 2eefd6ae9..211ef5f8b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java +++ b/java/src/com/android/inputmethod/keyboard/internal/RoundedLine.java @@ -37,16 +37,18 @@ public final class RoundedLine { * @param p2x the x-coordinate of the end point. * @param p2y the y-coordinate of the end point. * @param r2 the radius at the end point - * @return the path of rounded line + * @return an instance of {@link Path} that holds the result rounded line, or an instance of + * {@link Path} that holds an empty path if the start and end points are equal. */ public Path makePath(final float p1x, final float p1y, final float r1, final float p2x, final float p2y, final float r2) { + mPath.rewind(); final double dx = p2x - p1x; final double dy = p2y - p1y; // Distance of the points. final double l = Math.hypot(dx, dy); if (Double.compare(0.0d, l) == 0) { - return null; + return mPath; // Return an empty path } // Angle of the line p1-p2 final double a = Math.atan2(dy, dx); @@ -86,7 +88,6 @@ public final class RoundedLine { mArc2.set(p2x, p2y, p2x, p2y); mArc2.inset(-r2, -r2); - mPath.rewind(); // Trail cap at P1. mPath.moveTo(p1x, p1y); mPath.arcTo(mArc1, angle, a1); diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java index df7bad8d0..9d478491a 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java @@ -30,6 +30,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.Locale; /** @@ -301,12 +302,14 @@ public class DictionaryInfoUtils { private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList, final DictionaryInfo newElement) { - for (final DictionaryInfo info : dictList) { - if (info.mLocale.equals(newElement.mLocale)) { - if (newElement.mVersion <= info.mVersion) { + final Iterator<DictionaryInfo> iter = dictList.iterator(); + while (iter.hasNext()) { + final DictionaryInfo thisDictInfo = iter.next(); + if (thisDictInfo.mLocale.equals(newElement.mLocale)) { + if (newElement.mVersion <= thisDictInfo.mVersion) { return; } - dictList.remove(info); + iter.remove(); } } dictList.add(newElement); diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 0dd302afa..94513e635 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -54,13 +54,6 @@ public final class RichInputMethodManager { return sInstance; } - // Caveat: This may cause IPC - public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) { - // Basically called to check whether this IME has been triggered by the current user or not - return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)). - getInputMethodList().isEmpty(); - } - public static void init(final Context context) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); sInstance.initInternal(context, prefs); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 9fefb58a6..a6149c6ec 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.preference.PreferenceManager; +import android.util.Log; import com.android.inputmethod.latin.LocaleUtils.RunInLocale; @@ -28,6 +29,7 @@ import java.util.HashMap; import java.util.Locale; public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = Settings.class.getSimpleName(); // In the same order as xml/prefs.xml public static final String PREF_GENERAL_SETTINGS = "general_settings"; public static final String PREF_AUTO_CAP = "auto_cap"; @@ -114,6 +116,12 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { + if (mSettingsValues == null) { + // TODO: Introduce a static function to register this class and ensure that + // loadSettings must be called before "onSharedPreferenceChanged" is called. + Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); + return; + } loadSettings(mCurrentLocale, mSettingsValues.mInputAttributes); } diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index 20ce6d4e0..1fad765d7 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -32,6 +32,7 @@ import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; +import android.util.Log; import android.view.inputmethod.InputMethodSubtype; import java.util.TreeSet; @@ -41,10 +42,12 @@ import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; import com.android.inputmethod.latin.userdictionary.UserDictionaryList; import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; +import com.android.inputmethod.research.ResearchLogger; import com.android.inputmethodcommon.InputMethodSettingsFragment; public final class SettingsFragment extends InputMethodSettingsFragment implements SharedPreferences.OnSharedPreferenceChangeListener { + private static final String TAG = SettingsFragment.class.getSimpleName(); private static final boolean DBG_USE_INTERNAL_USER_DICTIONARY_SETTINGS = false; private ListPreference mVoicePreference; @@ -128,7 +131,12 @@ public final class SettingsFragment extends InputMethodSettingsFragment feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() { @Override public boolean onPreferenceClick(final Preference pref) { - FeedbackUtils.showFeedbackForm(getActivity()); + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + // Use development-only feedback mechanism + ResearchLogger.getInstance().presentFeedbackDialogFromSettings(); + } else { + FeedbackUtils.showFeedbackForm(getActivity()); + } return true; } }); @@ -139,6 +147,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment miscSettings.removePreference(aboutSettings); } } + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + // The about screen contains items that may be confusing in development-only versions. + miscSettings.removePreference(aboutSettings); + } final boolean showVoiceKeyOption = res.getBoolean( R.bool.config_enable_show_voice_key_option); @@ -244,7 +256,14 @@ public final class SettingsFragment extends InputMethodSettingsFragment @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - (new BackupManager(getActivity())).dataChanged(); + final Activity activity = getActivity(); + if (activity == null) { + // TODO: Introduce a static function to register this class and ensure that + // onCreate must be called before "onSharedPreferenceChanged" is called. + Log.w(TAG, "onSharedPreferenceChanged called before activity starts."); + return; + } + (new BackupManager(activity)).dataChanged(); final Resources res = getResources(); if (key.equals(Settings.PREF_POPUP_ON)) { setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 282b5794f..1eca68ae5 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -43,20 +43,23 @@ public final class SubtypeSwitcher { private static final String TAG = SubtypeSwitcher.class.getSimpleName(); private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); + private /* final */ RichInputMethodManager mRichImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; - /*-----------------------------------------------------------*/ - // Variants which should be changed only by reload functions. - private NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); + private final NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); private InputMethodInfo mShortcutInputMethodInfo; private InputMethodSubtype mShortcutSubtype; private InputMethodSubtype mNoLanguageSubtype; - /*-----------------------------------------------------------*/ - private boolean mIsNetworkConnected; + // Dummy no language QWERTY subtype. See {@link R.xml.method}. + private static final InputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE = new InputMethodSubtype( + R.string.subtype_no_language_qwerty, R.drawable.ic_subtype_keyboard, "zz", "keyboard", + "KeyboardLayoutSet=qwerty,AsciiCapable,EnabledWhenDefaultIsNotAsciiCapable", + false /* isAuxiliary */, false /* overridesImplicitlyEnabledSubtype */); + static final class NeedsToDisplayLanguage { private int mEnabledSubtypeCount; private boolean mIsSystemLanguageSameAsInputLanguage; @@ -96,11 +99,6 @@ public final class SubtypeSwitcher { mRichImm = RichInputMethodManager.getInstance(); mConnectivityManager = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE); - mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( - SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); - if (mNoLanguageSubtype == null) { - throw new RuntimeException("Can't find no lanugage with QWERTY subtype"); - } final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); mIsNetworkConnected = (info != null && info.isConnected()); @@ -255,10 +253,20 @@ public final class SubtypeSwitcher { } public InputMethodSubtype getCurrentSubtype() { - return mRichImm.getCurrentInputMethodSubtype(mNoLanguageSubtype); + return mRichImm.getCurrentInputMethodSubtype(getNoLanguageSubtype()); } public InputMethodSubtype getNoLanguageSubtype() { - return mNoLanguageSubtype; + if (mNoLanguageSubtype == null) { + mNoLanguageSubtype = mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( + SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); + } + if (mNoLanguageSubtype != null) { + return mNoLanguageSubtype; + } + Log.w(TAG, "Can't find no lanugage with QWERTY subtype"); + Log.w(TAG, "No input method subtype found; return dummy subtype: " + + DUMMY_NO_LANGUAGE_SUBTYPE); + return DUMMY_NO_LANGUAGE_SUBTYPE; } } diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java index 604ebeeb6..63d2fecd3 100644 --- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java +++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java @@ -25,9 +25,9 @@ import android.content.pm.PackageManager; import android.os.Process; import android.preference.PreferenceManager; import android.util.Log; +import android.view.inputmethod.InputMethodManager; import com.android.inputmethod.compat.IntentCompatUtils; -import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.Settings; /** @@ -65,17 +65,16 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver { } // The process that hosts this broadcast receiver is invoked and remains alive even after - // 1) the package has been re-installed, 2) the device has been booted, - // 3) a multiuser has been created. + // 1) the package has been re-installed, 2) the device has just booted, + // 3) a new user has been created. // There is no good reason to keep the process alive if this IME isn't a current IME. - final boolean isCurrentImeOfCurrentUser; - if (RichInputMethodManager.isInputMethodManagerValidForUserOfThisProcess(context)) { - RichInputMethodManager.init(context); - isCurrentImeOfCurrentUser = SetupActivity.isThisImeCurrent(context); - } else { - isCurrentImeOfCurrentUser = false; - } - + final InputMethodManager imm = + (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); + // Called to check whether this IME has been triggered by the current user or not + final boolean isInputMethodManagerValidForUserOfThisProcess = + !imm.getInputMethodList().isEmpty(); + final boolean isCurrentImeOfCurrentUser = isInputMethodManagerValidForUserOfThisProcess + && SetupActivity.isThisImeCurrent(context, imm); if (!isCurrentImeOfCurrentUser) { final int myPid = Process.myPid(); Log.i(TAG, "Killing my process: pid=" + myPid); diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java index 8a2de887d..a68f98fe7 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java @@ -24,8 +24,6 @@ import android.provider.Settings; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import com.android.inputmethod.latin.RichInputMethodManager; - public final class SetupActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { @@ -40,17 +38,24 @@ public final class SetupActivity extends Activity { } } + /* + * We may not be able to get our own {@link InputMethodInfo} just after this IME is installed + * because {@link InputMethodManagerService} may not be aware of this IME yet. + * Note: {@link RichInputMethodManager} has similar methods. Here in setup wizard, we can't + * use it for the reason above. + */ + /** * Check if the IME specified by the context is enabled. - * Note that {@link RichInputMethodManager} must have been initialized before calling this - * method. + * CAVEAT: This may cause a round trip IPC. * * @param context package context of the IME to be checked. + * @param imm the {@link InputMethodManager}. * @return true if this IME is enabled. */ - public static boolean isThisImeEnabled(final Context context) { + /* package */ static boolean isThisImeEnabled(final Context context, + final InputMethodManager imm) { final String packageName = context.getPackageName(); - final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager(); for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { if (packageName.equals(imi.getPackageName())) { return true; @@ -61,17 +66,36 @@ public final class SetupActivity extends Activity { /** * Check if the IME specified by the context is the current IME. - * Note that {@link RichInputMethodManager} must have been initialized before calling this - * method. + * CAVEAT: This may cause a round trip IPC. * * @param context package context of the IME to be checked. + * @param imm the {@link InputMethodManager}. * @return true if this IME is the current IME. */ - public static boolean isThisImeCurrent(final Context context) { - final InputMethodInfo myImi = - RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + /* package */ static boolean isThisImeCurrent(final Context context, + final InputMethodManager imm) { + final InputMethodInfo imi = getInputMethodInfoOf(context.getPackageName(), imm); final String currentImeId = Settings.Secure.getString( context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); - return myImi.getId().equals(currentImeId); + return imi != null && imi.getId().equals(currentImeId); + } + + /** + * Get {@link InputMethodInfo} of the IME specified by the package name. + * CAVEAT: This may cause a round trip IPC. + * + * @param packageName package name of the IME. + * @param imm the {@link InputMethodManager}. + * @return the {@link InputMethodInfo} of the IME specified by the <code>packageName</code>, + * or null if not found. + */ + /* package */ static InputMethodInfo getInputMethodInfoOf(final String packageName, + final InputMethodManager imm) { + for (final InputMethodInfo imi : imm.getInputMethodList()) { + if (packageName.equals(imi.getPackageName())) { + return imi; + } + } + return null; } } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index 78a6478c6..13fa9d9c8 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -28,6 +28,7 @@ import android.provider.Settings; import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; import android.widget.ImageView; import android.widget.TextView; import android.widget.VideoView; @@ -36,7 +37,6 @@ import com.android.inputmethod.compat.TextViewCompatUtils; import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SettingsActivity; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; @@ -48,6 +48,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private static final boolean ENABLE_WELCOME_VIDEO = true; + private InputMethodManager mImm; + private View mSetupWizard; private View mWelcomeScreen; private View mSetupScreen; @@ -69,15 +71,19 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private static final int STEP_LAUNCHING_IME_SETTINGS = 4; private static final int STEP_BACK_FROM_IME_SETTINGS = 5; - final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); + private SettingsPoolingHandler mHandler; - static final class SettingsPoolingHandler + private static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupWizardActivity> { private static final int MSG_POLLING_IME_SETTINGS = 0; private static final long IME_SETTINGS_POLLING_INTERVAL = 200; - public SettingsPoolingHandler(final SetupWizardActivity outerInstance) { + private final InputMethodManager mImmInHandler; + + public SettingsPoolingHandler(final SetupWizardActivity outerInstance, + final InputMethodManager imm) { super(outerInstance); + mImmInHandler = imm; } @Override @@ -88,7 +94,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL } switch (msg.what) { case MSG_POLLING_IME_SETTINGS: - if (SetupActivity.isThisImeEnabled(setupWizardActivity)) { + if (SetupActivity.isThisImeEnabled(setupWizardActivity, mImmInHandler)) { setupWizardActivity.invokeSetupWizardOfThisIme(); return; } @@ -112,11 +118,12 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL setTheme(android.R.style.Theme_Translucent_NoTitleBar); super.onCreate(savedInstanceState); + mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); + mHandler = new SettingsPoolingHandler(this, mImm); + setContentView(R.layout.setup_wizard); mSetupWizard = findViewById(R.id.setup_wizard); - RichInputMethodManager.init(this); - if (savedInstanceState == null) { mStepNumber = determineSetupStepNumberFromLauncher(); } else { @@ -143,11 +150,12 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL R.string.setup_step1_title, R.string.setup_step1_instruction, R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, R.string.setup_step1_action); + final SettingsPoolingHandler handler = mHandler; step1.setAction(new Runnable() { @Override public void run() { invokeLanguageAndInputSettings(); - mHandler.startPollingImeSettings(); + handler.startPollingImeSettings(); } }); mSetupStepGroup.addStep(step1); @@ -265,14 +273,15 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL void invokeInputMethodPicker() { // Invoke input method picker. - RichInputMethodManager.getInstance().getInputMethodManager() - .showInputMethodPicker(); + mImm.showInputMethodPicker(); mNeedsToAdjustStepNumberToSystemState = true; } void invokeSubtypeEnablerOfThisIme() { - final InputMethodInfo imi = - RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + final InputMethodInfo imi = SetupActivity.getInputMethodInfoOf(getPackageName(), mImm); + if (imi == null) { + return; + } final Intent intent = new Intent(); intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); @@ -293,10 +302,10 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL private int determineSetupStepNumber() { mHandler.cancelPollingImeSettings(); - if (!SetupActivity.isThisImeEnabled(this)) { + if (!SetupActivity.isThisImeEnabled(this, mImm)) { return STEP_1; } - if (!SetupActivity.isThisImeCurrent(this)) { + if (!SetupActivity.isThisImeCurrent(this, mImm)) { return STEP_2; } return STEP_3; diff --git a/java/src/com/android/inputmethod/latin/utils/Base64Reader.java b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java new file mode 100644 index 000000000..3eca6e744 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/Base64Reader.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2013 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.utils; + +import com.android.inputmethod.annotations.UsedForTesting; + +import java.io.EOFException; +import java.io.IOException; +import java.io.LineNumberReader; + +@UsedForTesting +public class Base64Reader { + private final LineNumberReader mReader; + + private String mLine; + private int mCharPos; + private int mByteCount; + + @UsedForTesting + public Base64Reader(final LineNumberReader reader) { + mReader = reader; + reset(); + } + + @UsedForTesting + public void reset() { + mLine = null; + mCharPos = 0; + mByteCount = 0; + } + + @UsedForTesting + public int getLineNumber() { + return mReader.getLineNumber(); + } + + @UsedForTesting + public int getByteCount() { + return mByteCount; + } + + private void fillBuffer() throws IOException { + if (mLine == null || mCharPos >= mLine.length()) { + mLine = mReader.readLine(); + mCharPos = 0; + } + if (mLine == null) { + throw new EOFException(); + } + } + + private int peekUint8() throws IOException { + fillBuffer(); + final char c = mLine.charAt(mCharPos); + if (c >= 'A' && c <= 'Z') + return c - 'A' + 0; + if (c >= 'a' && c <= 'z') + return c - 'a' + 26; + if (c >= '0' && c <= '9') + return c - '0' + 52; + if (c == '+') + return 62; + if (c == '/') + return 63; + if (c == '=') + return 0; + throw new RuntimeException("Unknown character '" + c + "' in base64 at line " + + mReader.getLineNumber()); + } + + private int getUint8() throws IOException { + final int value = peekUint8(); + mCharPos++; + return value; + } + + @UsedForTesting + public int readUint8() throws IOException { + final int value1, value2; + switch (mByteCount % 3) { + case 0: + value1 = getUint8() << 2; + value2 = value1 | (peekUint8() >> 4); + break; + case 1: + value1 = (getUint8() & 0x0f) << 4; + value2 = value1 | (peekUint8() >> 2); + break; + default: + value1 = (getUint8() & 0x03) << 6; + value2 = value1 | getUint8(); + break; + } + mByteCount++; + return value2; + } + + @UsedForTesting + public short readInt16() throws IOException { + final int data = readUint8() << 8; + return (short)(data | readUint8()); + } +} diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index aa4a866b8..e890b74aa 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -429,6 +429,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); resetLogBuffers(); + cancelFeedbackDialog(); } public void abort() { @@ -465,6 +466,12 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang presentFeedbackDialog(latinIME); } + public void presentFeedbackDialogFromSettings() { + if (mLatinIME != null) { + presentFeedbackDialog(mLatinIME); + } + } + public void presentFeedbackDialog(final LatinIME latinIME) { if (isMakingUserRecording()) { saveRecording(); @@ -701,13 +708,19 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mInFeedbackDialog = false; } + private void cancelFeedbackDialog() { + if (isMakingUserRecording()) { + cancelRecording(); + } + mInFeedbackDialog = false; + } + public void initSuggest(final Suggest suggest) { mSuggest = suggest; // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create // a new one. if (mMainLogBuffer != null) { - stop(); - start(); + restart(); } } diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 7ca405752..1cdfbe4d1 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -47,15 +47,15 @@ LATIN_IME_JNI_SRC_FILES := \ LATIN_IME_CORE_SRC_FILES := \ suggest/core/suggest.cpp \ - $(addprefix obsolete/, \ - correction.cpp) \ $(addprefix suggest/core/dicnode/, \ dic_node.cpp \ dic_node_utils.cpp \ dic_nodes_cache.cpp) \ $(addprefix suggest/core/dictionary/, \ bigram_dictionary.cpp \ - binary_dictionary_format.cpp \ + binary_dictionary_format_utils.cpp \ + binary_dictionary_header.cpp \ + binary_dictionary_header_reading_utils.cpp \ byte_array_utils.cpp \ dictionary.cpp \ digraph_utils.cpp) \ @@ -74,7 +74,9 @@ LATIN_IME_CORE_SRC_FILES := \ typing_suggest_policy.cpp \ typing_traversal.cpp \ typing_weighting.cpp) \ - utils/char_utils.cpp + $(addprefix utils/, \ + char_utils.cpp \ + autocorrection_threshold_utils.cpp) LOCAL_SRC_FILES := \ $(LATIN_IME_JNI_SRC_FILES) \ diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp index f60793733..1225e7f7a 100644 --- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp +++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp @@ -22,15 +22,16 @@ #include <cstring> // for memset() #include <fcntl.h> #include <sys/mman.h> +#include <unistd.h> #include "defines.h" #include "jni.h" #include "jni_common.h" -#include "obsolete/correction.h" -#include "suggest/core/dictionary/binary_dictionary_format.h" +#include "suggest/core/dictionary/binary_dictionary_format_utils.h" #include "suggest/core/dictionary/binary_dictionary_info.h" #include "suggest/core/dictionary/dictionary.h" #include "suggest/core/suggest_options.h" +#include "utils/autocorrection_threshold_utils.h" namespace latinime { @@ -201,7 +202,7 @@ static jfloat latinime_BinaryDictionary_calcNormalizedScore(JNIEnv *env, jclass int afterCodePoints[afterLength]; env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints); env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints); - return Correction::RankingAlgorithm::calcNormalizedScore(beforeCodePoints, beforeLength, + return AutocorrectionThresholdUtils::calcNormalizedScore(beforeCodePoints, beforeLength, afterCodePoints, afterLength, score); } @@ -213,7 +214,7 @@ static jint latinime_BinaryDictionary_editDistance(JNIEnv *env, jclass clazz, ji int afterCodePoints[afterLength]; env->GetIntArrayRegion(before, 0, beforeLength, beforeCodePoints); env->GetIntArrayRegion(after, 0, afterLength, afterCodePoints); - return Correction::RankingAlgorithm::editDistance(beforeCodePoints, beforeLength, + return AutocorrectionThresholdUtils::editDistance(beforeCodePoints, beforeLength, afterCodePoints, afterLength); } diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h index e0edff584..a3cf6a4b4 100644 --- a/native/jni/src/defines.h +++ b/native/jni/src/defines.h @@ -268,9 +268,6 @@ static inline void prof_out(void) { #define NOT_A_CODE_POINT (-1) #define NOT_A_DISTANCE (-1) #define NOT_A_COORDINATE (-1) -#define MATCH_CHAR_WITHOUT_DISTANCE_INFO (-2) -#define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO (-3) -#define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO (-4) #define NOT_AN_INDEX (-1) #define NOT_A_PROBABILITY (-1) @@ -278,23 +275,13 @@ static inline void prof_out(void) { #define KEYCODE_SINGLE_QUOTE '\'' #define KEYCODE_HYPHEN_MINUS '-' -#define CALIBRATE_SCORE_BY_TOUCH_COORDINATES true -#define SUGGEST_MULTIPLE_WORDS true #define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f - -#define ZERO_DISTANCE_PROMOTION_RATE 110.0f -#define NEUTRAL_SCORE_SQUARED_RADIUS 8.0f -#define HALF_SCORE_SQUARED_RADIUS 32.0f #define MAX_PROBABILITY 255 #define MAX_BIGRAM_ENCODED_PROBABILITY 15 -#define MULTIPLE_WORDS_DEMOTION_RATE 80 // Assuming locale strings such as en_US, sr-Latn etc. #define MAX_LOCALE_STRING_LENGTH 10 -/* heuristic... This should be changed if we change the unit of the probability. */ -#define SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ (MAX_PROBABILITY * 58 / 100) - // Max value for length, distance and probability which are used in weighting // TODO: Remove #define MAX_VALUE_FOR_WEIGHTING 10000000 diff --git a/native/jni/src/obsolete/correction.cpp b/native/jni/src/obsolete/correction.cpp deleted file mode 100644 index 6b80ed8ea..000000000 --- a/native/jni/src/obsolete/correction.cpp +++ /dev/null @@ -1,1006 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#define LOG_TAG "LatinIME: correction.cpp" - -#include <cmath> - -#include "defines.h" -#include "obsolete/correction.h" -#include "suggest/core/layout/proximity_info_state.h" -#include "suggest/core/layout/touch_position_correction_utils.h" -#include "suggest/policyimpl/utils/edit_distance.h" -#include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h" -#include "utils/char_utils.h" - -namespace latinime { - -class ProximityInfo; - -// private static const member variables -// The following "rate"s are used as a multiplier before dividing by 100, so they are in percent. -const int Correction::WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE = 80; -const int Correction::WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X = 12; -const int Correction::WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE = 58; -const int Correction::WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE = 50; -const int Correction::WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE = 75; -const int Correction::WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE = 75; -const int Correction::WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE = 70; -const int Correction::FULL_MATCHED_WORDS_PROMOTION_RATE = 120; -const int Correction::WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE = 90; -const int Correction::WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE = 70; -const int Correction::WORDS_WITH_MATCH_SKIP_PROMOTION_RATE = 105; -const int Correction::WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE = 148; -const int Correction::WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER = 3; -const int Correction::CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE = 45; -const int Correction::INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE = 70; -const int Correction::FIRST_CHAR_DIFFERENT_DEMOTION_RATE = 96; -const int Correction::TWO_WORDS_CAPITALIZED_DEMOTION_RATE = 50; -const int Correction::TWO_WORDS_CORRECTION_DEMOTION_BASE = 80; - -///////////////////////////// -// edit distance funcitons // -///////////////////////////// - -inline static void initEditDistance(int *editDistanceTable) { - for (int i = 0; i <= MAX_WORD_LENGTH; ++i) { - editDistanceTable[i] = i; - } -} - -inline static void dumpEditDistance10ForDebug(int *editDistanceTable, - const int editDistanceTableWidth, const int outputLength) { - if (DEBUG_DICT) { - AKLOGI("EditDistanceTable"); - for (int i = 0; i <= 10; ++i) { - int c[11]; - for (int j = 0; j <= 10; ++j) { - if (j < editDistanceTableWidth + 1 && i < outputLength + 1) { - c[j] = (editDistanceTable + i * (editDistanceTableWidth + 1))[j]; - } else { - c[j] = -1; - } - } - AKLOGI("[ %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d ]", - c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]); - (void)c; // To suppress compiler warning - } - } -} - -inline static int getCurrentEditDistance(int *editDistanceTable, const int editDistanceTableWidth, - const int outputLength, const int inputSize) { - if (DEBUG_EDIT_DISTANCE) { - AKLOGI("getCurrentEditDistance %d, %d", inputSize, outputLength); - } - return editDistanceTable[(editDistanceTableWidth + 1) * (outputLength) + inputSize]; -} - -//////////////// -// Correction // -//////////////// - -void Correction::resetCorrection() { - mTotalTraverseCount = 0; -} - -void Correction::initCorrection(const ProximityInfo *pi, const int inputSize, const int maxDepth) { - mProximityInfo = pi; - mInputSize = inputSize; - mMaxDepth = maxDepth; - mMaxEditDistance = mInputSize < 5 ? 2 : mInputSize / 2; - // TODO: This is not supposed to be required. Check what's going wrong with - // editDistance[0 ~ MAX_WORD_LENGTH] - initEditDistance(mEditDistanceTable); -} - -void Correction::initCorrectionState( - const int rootPos, const int childCount, const bool traverseAll) { - latinime::initCorrectionState(mCorrectionStates, rootPos, childCount, traverseAll); - // TODO: remove - mCorrectionStates[0].mTransposedPos = mTransposedPos; - mCorrectionStates[0].mExcessivePos = mExcessivePos; - mCorrectionStates[0].mSkipPos = mSkipPos; -} - -void Correction::setCorrectionParams(const int skipPos, const int excessivePos, - const int transposedPos, const int spaceProximityPos, const int missingSpacePos, - const bool useFullEditDistance, const bool doAutoCompletion, const int maxErrors) { - // TODO: remove - mTransposedPos = transposedPos; - mExcessivePos = excessivePos; - mSkipPos = skipPos; - // TODO: remove - mCorrectionStates[0].mTransposedPos = transposedPos; - mCorrectionStates[0].mExcessivePos = excessivePos; - mCorrectionStates[0].mSkipPos = skipPos; - - mSpaceProximityPos = spaceProximityPos; - mMissingSpacePos = missingSpacePos; - mUseFullEditDistance = useFullEditDistance; - mDoAutoCompletion = doAutoCompletion; - mMaxErrors = maxErrors; -} - -void Correction::checkState() const { - if (DEBUG_DICT) { - int inputCount = 0; - if (mSkipPos >= 0) ++inputCount; - if (mExcessivePos >= 0) ++inputCount; - if (mTransposedPos >= 0) ++inputCount; - } -} - -bool Correction::sameAsTyped() const { - return mProximityInfoState.sameAsTyped(mWord, mOutputIndex); -} - -int Correction::getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, - const int wordCount, const bool isSpaceProximity, const int *word) const { - return Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(freqArray, wordLengthArray, - wordCount, this, isSpaceProximity, word); -} - -int Correction::getFinalProbability(const int probability, int **word, int *wordLength) { - return getFinalProbabilityInternal(probability, word, wordLength, mInputSize); -} - -int Correction::getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength, - const int inputSize) { - return getFinalProbabilityInternal(probability, word, wordLength, inputSize); -} - -bool Correction::initProcessState(const int outputIndex) { - if (mCorrectionStates[outputIndex].mChildCount <= 0) { - return false; - } - mOutputIndex = outputIndex; - --(mCorrectionStates[outputIndex].mChildCount); - mInputIndex = mCorrectionStates[outputIndex].mInputIndex; - mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes; - - mEquivalentCharCount = mCorrectionStates[outputIndex].mEquivalentCharCount; - mProximityCount = mCorrectionStates[outputIndex].mProximityCount; - mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount; - mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount; - mSkippedCount = mCorrectionStates[outputIndex].mSkippedCount; - mLastCharExceeded = mCorrectionStates[outputIndex].mLastCharExceeded; - - mTransposedPos = mCorrectionStates[outputIndex].mTransposedPos; - mExcessivePos = mCorrectionStates[outputIndex].mExcessivePos; - mSkipPos = mCorrectionStates[outputIndex].mSkipPos; - - mMatching = false; - mProximityMatching = false; - mAdditionalProximityMatching = false; - mTransposing = false; - mExceeding = false; - mSkipping = false; - - return true; -} - -int Correction::goDownTree(const int parentIndex, const int childCount, const int firstChildPos) { - mCorrectionStates[mOutputIndex].mParentIndex = parentIndex; - mCorrectionStates[mOutputIndex].mChildCount = childCount; - mCorrectionStates[mOutputIndex].mSiblingPos = firstChildPos; - return mOutputIndex; -} - -// TODO: remove -int Correction::getInputIndex() const { - return mInputIndex; -} - -bool Correction::needsToPrune() const { - // TODO: use edit distance here - return mOutputIndex - 1 >= mMaxDepth || mProximityCount > mMaxEditDistance - // Allow one char longer word for missing character - || (!mDoAutoCompletion && (mOutputIndex > mInputSize)); -} - -inline static bool isEquivalentChar(ProximityType type) { - return type == MATCH_CHAR; -} - -inline static bool isProximityCharOrEquivalentChar(ProximityType type) { - return type == MATCH_CHAR || type == PROXIMITY_CHAR; -} - -Correction::CorrectionType Correction::processCharAndCalcState(const int c, const bool isTerminal) { - const int correctionCount = (mSkippedCount + mExcessiveCount + mTransposedCount); - if (correctionCount > mMaxErrors) { - return processUnrelatedCorrectionType(); - } - - // TODO: Change the limit if we'll allow two or more corrections - const bool noCorrectionsHappenedSoFar = correctionCount == 0; - const bool canTryCorrection = noCorrectionsHappenedSoFar; - int proximityIndex = 0; - mDistances[mOutputIndex] = NOT_A_DISTANCE; - - // Skip checking this node - if (mNeedsToTraverseAllNodes || isSingleQuote(c)) { - bool incremented = false; - if (mLastCharExceeded && mInputIndex == mInputSize - 1) { - // TODO: Do not check the proximity if EditDistance exceeds the threshold - const ProximityType matchId = mProximityInfoState.getProximityType( - mInputIndex, c, true, &proximityIndex); - if (isEquivalentChar(matchId)) { - mLastCharExceeded = false; - --mExcessiveCount; - mDistances[mOutputIndex] = - mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0); - } else if (matchId == PROXIMITY_CHAR) { - mLastCharExceeded = false; - --mExcessiveCount; - ++mProximityCount; - mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance( - mInputIndex, proximityIndex); - } - if (!isSingleQuote(c)) { - incrementInputIndex(); - incremented = true; - } - } - return processSkipChar(c, isTerminal, incremented); - } - - // Check possible corrections. - if (mExcessivePos >= 0) { - if (mExcessiveCount == 0 && mExcessivePos < mOutputIndex) { - mExcessivePos = mOutputIndex; - } - if (mExcessivePos < mInputSize - 1) { - mExceeding = mExcessivePos == mInputIndex && canTryCorrection; - } - } - - if (mSkipPos >= 0) { - if (mSkippedCount == 0 && mSkipPos < mOutputIndex) { - if (DEBUG_DICT) { - // TODO: Enable this assertion. - //ASSERT(mSkipPos == mOutputIndex - 1); - } - mSkipPos = mOutputIndex; - } - mSkipping = mSkipPos == mOutputIndex && canTryCorrection; - } - - if (mTransposedPos >= 0) { - if (mTransposedCount == 0 && mTransposedPos < mOutputIndex) { - mTransposedPos = mOutputIndex; - } - if (mTransposedPos < mInputSize - 1) { - mTransposing = mInputIndex == mTransposedPos && canTryCorrection; - } - } - - bool secondTransposing = false; - if (mTransposedCount % 2 == 1) { - if (isEquivalentChar(mProximityInfoState.getProximityType( - mInputIndex - 1, c, false))) { - ++mTransposedCount; - secondTransposing = true; - } else if (mCorrectionStates[mOutputIndex].mExceeding) { - --mTransposedCount; - ++mExcessiveCount; - --mExcessivePos; - incrementInputIndex(); - } else { - --mTransposedCount; - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - DUMP_WORD(mWord, mOutputIndex); - AKLOGI("UNRELATED(0): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - return processUnrelatedCorrectionType(); - } - } - - // TODO: Change the limit if we'll allow two or more proximity chars with corrections - // Work around: When the mMaxErrors is 1, we only allow just one error - // including proximity correction. - const bool checkProximityChars = (mMaxErrors > 1) - ? (noCorrectionsHappenedSoFar || mProximityCount == 0) - : (noCorrectionsHappenedSoFar && mProximityCount == 0); - - ProximityType matchedProximityCharId = secondTransposing - ? MATCH_CHAR - : mProximityInfoState.getProximityType( - mInputIndex, c, checkProximityChars, &proximityIndex); - - if (SUBSTITUTION_CHAR == matchedProximityCharId - || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) { - if (canTryCorrection && mOutputIndex > 0 - && mCorrectionStates[mOutputIndex].mProximityMatching - && mCorrectionStates[mOutputIndex].mExceeding - && isEquivalentChar(mProximityInfoState.getProximityType( - mInputIndex, mWord[mOutputIndex - 1], false))) { - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - AKLOGI("CONVERSION p->e %c", mWord[mOutputIndex - 1]); - } - // Conversion p->e - // Example: - // wearth -> earth - // px -> (E)mmmmm - ++mExcessiveCount; - --mProximityCount; - mExcessivePos = mOutputIndex - 1; - ++mInputIndex; - // Here, we are doing something equivalent to matchedProximityCharId, - // but we already know that "excessive char correction" just happened - // so that we just need to check "mProximityCount == 0". - matchedProximityCharId = mProximityInfoState.getProximityType( - mInputIndex, c, mProximityCount == 0, &proximityIndex); - } - } - - if (SUBSTITUTION_CHAR == matchedProximityCharId - || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) { - if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) { - mAdditionalProximityMatching = true; - } - // TODO: Optimize - // As the current char turned out to be an unrelated char, - // we will try other correction-types. Please note that mCorrectionStates[mOutputIndex] - // here refers to the previous state. - if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0 - && !mCorrectionStates[mOutputIndex].mTransposing - && mCorrectionStates[mOutputIndex - 1].mTransposing - && isEquivalentChar(mProximityInfoState.getProximityType( - mInputIndex, mWord[mOutputIndex - 1], false)) - && isEquivalentChar( - mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) { - // Conversion t->e - // Example: - // occaisional -> occa sional - // mmmmttx -> mmmm(E)mmmmmm - mTransposedCount -= 2; - ++mExcessiveCount; - ++mInputIndex; - } else if (mOutputIndex > 0 && mInputIndex > 0 && mTransposedCount > 0 - && !mCorrectionStates[mOutputIndex].mTransposing - && mCorrectionStates[mOutputIndex - 1].mTransposing - && isEquivalentChar( - mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) { - // Conversion t->s - // Example: - // chcolate -> chocolate - // mmttx -> mmsmmmmmm - mTransposedCount -= 2; - ++mSkippedCount; - --mInputIndex; - } else if (canTryCorrection && mInputIndex > 0 - && mCorrectionStates[mOutputIndex].mProximityMatching - && mCorrectionStates[mOutputIndex].mSkipping - && isEquivalentChar( - mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) { - // Conversion p->s - // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of - // proximity chars of "s", but it should rather be handled as a skipped char. - ++mSkippedCount; - --mProximityCount; - return processSkipChar(c, isTerminal, false); - } else if (mInputIndex - 1 < mInputSize - && mSkippedCount > 0 - && mCorrectionStates[mOutputIndex].mSkipping - && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching - && isProximityCharOrEquivalentChar( - mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) { - // Conversion s->a - incrementInputIndex(); - --mSkippedCount; - mProximityMatching = true; - ++mProximityCount; - mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO; - } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize - && isEquivalentChar( - mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) { - // 1.2. Excessive or transpose correction - if (mTransposing) { - ++mTransposedCount; - } else { - ++mExcessiveCount; - incrementInputIndex(); - } - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - DUMP_WORD(mWord, mOutputIndex); - if (mTransposing) { - AKLOGI("TRANSPOSE: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } else { - AKLOGI("EXCEED: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - } - } else if (mSkipping) { - // 3. Skip correction - ++mSkippedCount; - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - AKLOGI("SKIP: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - return processSkipChar(c, isTerminal, false); - } else if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) { - // As a last resort, use additional proximity characters - mProximityMatching = true; - ++mProximityCount; - mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO; - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - AKLOGI("ADDITIONALPROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - } else { - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - DUMP_WORD(mWord, mOutputIndex); - AKLOGI("UNRELATED(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - return processUnrelatedCorrectionType(); - } - } else if (secondTransposing) { - // If inputIndex is greater than mInputSize, that means there is no - // proximity chars. So, we don't need to check proximity. - mMatching = true; - } else if (isEquivalentChar(matchedProximityCharId)) { - mMatching = true; - ++mEquivalentCharCount; - mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0); - } else if (PROXIMITY_CHAR == matchedProximityCharId) { - mProximityMatching = true; - ++mProximityCount; - mDistances[mOutputIndex] = - mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, proximityIndex); - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 - || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - AKLOGI("PROX: %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - } - - addCharToCurrentWord(c); - - // 4. Last char excessive correction - mLastCharExceeded = mExcessiveCount == 0 && mSkippedCount == 0 && mTransposedCount == 0 - && mProximityCount == 0 && (mInputIndex == mInputSize - 2); - const bool isSameAsUserTypedLength = (mInputSize == mInputIndex + 1) || mLastCharExceeded; - if (mLastCharExceeded) { - ++mExcessiveCount; - } - - // Start traversing all nodes after the index exceeds the user typed length - if (isSameAsUserTypedLength) { - startToTraverseAllNodes(); - } - - const bool needsToTryOnTerminalForTheLastPossibleExcessiveChar = - mExceeding && mInputIndex == mInputSize - 2; - - // Finally, we are ready to go to the next character, the next "virtual node". - // We should advance the input index. - // We do this in this branch of the 'if traverseAllNodes' because we are still matching - // characters to input; the other branch is not matching them but searching for - // completions, this is why it does not have to do it. - incrementInputIndex(); - // Also, the next char is one "virtual node" depth more than this char. - incrementOutputIndex(); - - if ((needsToTryOnTerminalForTheLastPossibleExcessiveChar - || isSameAsUserTypedLength) && isTerminal) { - mTerminalInputIndex = mInputIndex - 1; - mTerminalOutputIndex = mOutputIndex - 1; - if (DEBUG_CORRECTION - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize) - && (MIN_OUTPUT_INDEX_FOR_DEBUG <= 0 || MIN_OUTPUT_INDEX_FOR_DEBUG < mOutputIndex)) { - DUMP_WORD(mWord, mOutputIndex); - AKLOGI("ONTERMINAL(1): %d, %d, %d, %d, %c", mProximityCount, mSkippedCount, - mTransposedCount, mExcessiveCount, c); - } - return ON_TERMINAL; - } else { - mTerminalInputIndex = mInputIndex - 1; - mTerminalOutputIndex = mOutputIndex - 1; - return NOT_ON_TERMINAL; - } -} - -inline static int getQuoteCount(const int *word, const int length) { - int quoteCount = 0; - for (int i = 0; i < length; ++i) { - if (word[i] == KEYCODE_SINGLE_QUOTE) { - ++quoteCount; - } - } - return quoteCount; -} - -inline static bool isUpperCase(unsigned short c) { - return CharUtils::isAsciiUpper(CharUtils::toBaseCodePoint(c)); -} - -////////////////////// -// RankingAlgorithm // -////////////////////// - -/* static */ int Correction::RankingAlgorithm::calculateFinalProbability(const int inputIndex, - const int outputIndex, const int freq, int *editDistanceTable, const Correction *correction, - const int inputSize) { - const int excessivePos = correction->getExcessivePos(); - const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER; - const int fullWordMultiplier = correction->FULL_WORD_MULTIPLIER; - const ProximityInfoState *proximityInfoState = &correction->mProximityInfoState; - const int skippedCount = correction->mSkippedCount; - const int transposedCount = correction->mTransposedCount / 2; - const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2; - const int proximityMatchedCount = correction->mProximityCount; - const bool lastCharExceeded = correction->mLastCharExceeded; - const bool useFullEditDistance = correction->mUseFullEditDistance; - const int outputLength = outputIndex + 1; - if (skippedCount >= inputSize || inputSize == 0) { - return -1; - } - - // TODO: find more robust way - bool sameLength = lastCharExceeded ? (inputSize == inputIndex + 2) - : (inputSize == inputIndex + 1); - - // TODO: use mExcessiveCount - const int matchCount = inputSize - correction->mProximityCount - excessiveCount; - - const int *word = correction->mWord; - const bool skipped = skippedCount > 0; - - const int quoteDiffCount = max(0, getQuoteCount(word, outputLength) - - getQuoteCount(proximityInfoState->getPrimaryInputWord(), inputSize)); - - // TODO: Calculate edit distance for transposed and excessive - int ed = 0; - if (DEBUG_DICT_FULL) { - dumpEditDistance10ForDebug(editDistanceTable, correction->mInputSize, outputLength); - } - int adjustedProximityMatchedCount = proximityMatchedCount; - - int finalFreq = freq; - - if (DEBUG_CORRECTION_FREQ - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) { - AKLOGI("FinalFreq0: %d", finalFreq); - } - // TODO: Optimize this. - if (transposedCount > 0 || proximityMatchedCount > 0 || skipped || excessiveCount > 0) { - ed = getCurrentEditDistance(editDistanceTable, correction->mInputSize, outputLength, - inputSize) - transposedCount; - - const int matchWeight = powerIntCapped(typedLetterMultiplier, - max(inputSize, outputLength) - ed); - multiplyIntCapped(matchWeight, &finalFreq); - - // TODO: Demote further if there are two or more excessive chars with longer user input? - if (inputSize > outputLength) { - multiplyRate(INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE, &finalFreq); - } - - ed = max(0, ed - quoteDiffCount); - adjustedProximityMatchedCount = min(max(0, ed - (outputLength - inputSize)), - proximityMatchedCount); - if (transposedCount <= 0) { - if (ed == 1 && (inputSize == outputLength - 1 || inputSize == outputLength + 1)) { - // Promote a word with just one skipped or excessive char - if (sameLength) { - multiplyRate(WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE - + WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER * outputLength, - &finalFreq); - } else { - multiplyIntCapped(typedLetterMultiplier, &finalFreq); - } - } else if (ed == 0) { - multiplyIntCapped(typedLetterMultiplier, &finalFreq); - sameLength = true; - } - } - } else { - const int matchWeight = powerIntCapped(typedLetterMultiplier, matchCount); - multiplyIntCapped(matchWeight, &finalFreq); - } - - if (proximityInfoState->getProximityType(0, word[0], true) == SUBSTITUTION_CHAR) { - multiplyRate(FIRST_CHAR_DIFFERENT_DEMOTION_RATE, &finalFreq); - } - - /////////////////////////////////////////////// - // Promotion and Demotion for each correction - - // Demotion for a word with missing character - if (skipped) { - const int demotionRate = WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE - * (10 * inputSize - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X) - / (10 * inputSize - - WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X + 10); - if (DEBUG_DICT_FULL) { - AKLOGI("Demotion rate for missing character is %d.", demotionRate); - } - multiplyRate(demotionRate, &finalFreq); - } - - // Demotion for a word with transposed character - if (transposedCount > 0) multiplyRate( - WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE, &finalFreq); - - // Demotion for a word with excessive character - if (excessiveCount > 0) { - multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); - if (!lastCharExceeded && !proximityInfoState->existsAdjacentProximityChars(excessivePos)) { - if (DEBUG_DICT_FULL) { - AKLOGI("Double excessive demotion"); - } - // If an excessive character is not adjacent to the left char or the right char, - // we will demote this word. - multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, &finalFreq); - } - } - - int additionalProximityCount = 0; - // Demote additional proximity characters - for (int i = 0; i < outputLength; ++i) { - const int squaredDistance = correction->mDistances[i]; - if (squaredDistance == ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO) { - ++additionalProximityCount; - } - } - - const bool performTouchPositionCorrection = - CALIBRATE_SCORE_BY_TOUCH_COORDINATES - && proximityInfoState->touchPositionCorrectionEnabled() - && skippedCount == 0 && excessiveCount == 0 && transposedCount == 0 - && additionalProximityCount == 0; - - // Score calibration by touch coordinates is being done only for pure-fat finger typing error - // cases. - // TODO: Remove this constraint. - if (performTouchPositionCorrection) { - for (int i = 0; i < outputLength; ++i) { - const int squaredDistance = correction->mDistances[i]; - if (i < adjustedProximityMatchedCount) { - multiplyIntCapped(typedLetterMultiplier, &finalFreq); - } - const float factor = TouchPositionCorrectionUtils::getLengthScalingFactor( - static_cast<float>(squaredDistance)); - if (factor > 0.0f) { - multiplyRate(static_cast<int>(factor * 100.0f), &finalFreq); - } else if (squaredDistance == PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO) { - multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); - } - } - } else { - // Promotion for a word with proximity characters - for (int i = 0; i < adjustedProximityMatchedCount; ++i) { - // A word with proximity corrections - if (DEBUG_DICT_FULL) { - AKLOGI("Found a proximity correction."); - } - multiplyIntCapped(typedLetterMultiplier, &finalFreq); - if (i < additionalProximityCount) { - multiplyRate(WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); - } else { - multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); - } - } - } - - // If the user types too many(three or more) proximity characters with additional proximity - // character,do not treat as the same length word. - if (sameLength && additionalProximityCount > 0 && (adjustedProximityMatchedCount >= 3 - || transposedCount > 0 || skipped || excessiveCount > 0)) { - sameLength = false; - } - - const int errorCount = adjustedProximityMatchedCount > 0 - ? adjustedProximityMatchedCount - : (proximityMatchedCount + transposedCount); - multiplyRate( - 100 - CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE * errorCount / inputSize, &finalFreq); - - // Promotion for an exactly matched word - if (ed == 0) { - // Full exact match - if (sameLength && transposedCount == 0 && !skipped && excessiveCount == 0 - && quoteDiffCount == 0 && additionalProximityCount == 0) { - finalFreq = capped255MultForFullMatchAccentsOrCapitalizationDifference(finalFreq); - } - } - - // Promote a word with no correction - if (proximityMatchedCount == 0 && transposedCount == 0 && !skipped && excessiveCount == 0 - && additionalProximityCount == 0) { - multiplyRate(FULL_MATCHED_WORDS_PROMOTION_RATE, &finalFreq); - } - - // TODO: Check excessive count and transposed count - // TODO: Remove this if possible - /* - If the last character of the user input word is the same as the next character - of the output word, and also all of characters of the user input are matched - to the output word, we'll promote that word a bit because - that word can be considered the combination of skipped and matched characters. - This means that the 'sm' pattern wins over the 'ma' pattern. - e.g.) - shel -> shell [mmmma] or [mmmsm] - hel -> hello [mmmaa] or [mmsma] - m ... matching - s ... skipping - a ... traversing all - t ... transposing - e ... exceeding - p ... proximity matching - */ - if (matchCount == inputSize && matchCount >= 2 && !skipped - && word[matchCount] == word[matchCount - 1]) { - multiplyRate(WORDS_WITH_MATCH_SKIP_PROMOTION_RATE, &finalFreq); - } - - // TODO: Do not use sameLength? - if (sameLength) { - multiplyIntCapped(fullWordMultiplier, &finalFreq); - } - - if (useFullEditDistance && outputLength > inputSize + 1) { - const int diff = outputLength - inputSize - 1; - const int divider = diff < 31 ? 1 << diff : S_INT_MAX; - finalFreq = divider > finalFreq ? 1 : finalFreq / divider; - } - - if (DEBUG_DICT_FULL) { - AKLOGI("calc: %d, %d", outputLength, sameLength); - } - - if (DEBUG_CORRECTION_FREQ - && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == inputSize)) { - DUMP_WORD(correction->getPrimaryInputWord(), inputSize); - DUMP_WORD(correction->mWord, outputLength); - AKLOGI("FinalFreq: [P%d, S%d, T%d, E%d, A%d] %d, %d, %d, %d, %d, %d", proximityMatchedCount, - skippedCount, transposedCount, excessiveCount, additionalProximityCount, - outputLength, lastCharExceeded, sameLength, quoteDiffCount, ed, finalFreq); - } - - return finalFreq; -} - -/* static */ int Correction::RankingAlgorithm::calcFreqForSplitMultipleWords(const int *freqArray, - const int *wordLengthArray, const int wordCount, const Correction *correction, - const bool isSpaceProximity, const int *word) { - const int typedLetterMultiplier = correction->TYPED_LETTER_MULTIPLIER; - - bool firstCapitalizedWordDemotion = false; - bool secondCapitalizedWordDemotion = false; - - { - // TODO: Handle multiple capitalized word demotion properly - const int firstWordLength = wordLengthArray[0]; - const int secondWordLength = wordLengthArray[1]; - if (firstWordLength >= 2) { - firstCapitalizedWordDemotion = isUpperCase(word[0]); - } - - if (secondWordLength >= 2) { - // FIXME: word[firstWordLength + 1] is incorrect. - secondCapitalizedWordDemotion = isUpperCase(word[firstWordLength + 1]); - } - } - - - const bool capitalizedWordDemotion = - firstCapitalizedWordDemotion ^ secondCapitalizedWordDemotion; - - int totalLength = 0; - int totalFreq = 0; - for (int i = 0; i < wordCount; ++i) { - const int wordLength = wordLengthArray[i]; - if (wordLength <= 0) { - return 0; - } - totalLength += wordLength; - const int demotionRate = 100 - TWO_WORDS_CORRECTION_DEMOTION_BASE / (wordLength + 1); - int tempFirstFreq = freqArray[i]; - multiplyRate(demotionRate, &tempFirstFreq); - totalFreq += tempFirstFreq; - } - - if (totalLength <= 0 || totalFreq <= 0) { - return 0; - } - - // TODO: Currently totalFreq is adjusted to two word metrix. - // Promote pairFreq with multiplying by 2, because the word length is the same as the typed - // length. - totalFreq = totalFreq * 2 / wordCount; - if (wordCount > 2) { - // Safety net for 3+ words -- Caveats: many heuristics and workarounds here. - int oneLengthCounter = 0; - int twoLengthCounter = 0; - for (int i = 0; i < wordCount; ++i) { - const int wordLength = wordLengthArray[i]; - // TODO: Use bigram instead of this safety net - if (i < wordCount - 1) { - const int nextWordLength = wordLengthArray[i + 1]; - if (wordLength == 1 && nextWordLength == 2) { - // Safety net to filter 1 length and 2 length sequential words - return 0; - } - } - const int freq = freqArray[i]; - // Demote too short weak words - if (wordLength <= 4 && freq <= SUPPRESS_SHORT_MULTIPLE_WORDS_THRESHOLD_FREQ) { - multiplyRate(100 * freq / MAX_PROBABILITY, &totalFreq); - } - if (wordLength == 1) { - ++oneLengthCounter; - } else if (wordLength == 2) { - ++twoLengthCounter; - } - if (oneLengthCounter >= 2 || (oneLengthCounter + twoLengthCounter) >= 4) { - // Safety net to filter too many short words - return 0; - } - } - multiplyRate(MULTIPLE_WORDS_DEMOTION_RATE, &totalFreq); - } - - // This is a workaround to try offsetting the not-enough-demotion which will be done in - // calcNormalizedScore in Utils.java. - // In calcNormalizedScore the score will be demoted by (1 - 1 / length) - // but we demoted only (1 - 1 / (length + 1)) so we will additionally adjust freq by - // (1 - 1 / length) / (1 - 1 / (length + 1)) = (1 - 1 / (length * length)) - const int normalizedScoreNotEnoughDemotionAdjustment = 100 - 100 / (totalLength * totalLength); - multiplyRate(normalizedScoreNotEnoughDemotionAdjustment, &totalFreq); - - // At this moment, totalFreq is calculated by the following formula: - // (firstFreq * (1 - 1 / (firstWordLength + 1)) + secondFreq * (1 - 1 / (secondWordLength + 1))) - // * (1 - 1 / totalLength) / (1 - 1 / (totalLength + 1)) - - multiplyIntCapped(powerIntCapped(typedLetterMultiplier, totalLength), &totalFreq); - - // This is another workaround to offset the demotion which will be done in - // calcNormalizedScore in Utils.java. - // In calcNormalizedScore the score will be demoted by (1 - 1 / length) so we have to promote - // the same amount because we already have adjusted the synthetic freq of this "missing or - // mistyped space" suggestion candidate above in this method. - const int normalizedScoreDemotionRateOffset = (100 + 100 / totalLength); - multiplyRate(normalizedScoreDemotionRateOffset, &totalFreq); - - if (isSpaceProximity) { - // A word pair with one space proximity correction - if (DEBUG_DICT) { - AKLOGI("Found a word pair with space proximity correction."); - } - multiplyIntCapped(typedLetterMultiplier, &totalFreq); - multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &totalFreq); - } - - if (isSpaceProximity) { - multiplyRate(WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE, &totalFreq); - } else { - multiplyRate(WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE, &totalFreq); - } - - if (capitalizedWordDemotion) { - multiplyRate(TWO_WORDS_CAPITALIZED_DEMOTION_RATE, &totalFreq); - } - - if (DEBUG_CORRECTION_FREQ) { - AKLOGI("Multiple words (%d, %d) (%d, %d) %d, %d", freqArray[0], freqArray[1], - wordLengthArray[0], wordLengthArray[1], capitalizedWordDemotion, totalFreq); - DUMP_WORD(word, wordLengthArray[0]); - } - - return totalFreq; -} - -/* static */ int Correction::RankingAlgorithm::editDistance(const int *before, - const int beforeLength, const int *after, const int afterLength) { - const DamerauLevenshteinEditDistancePolicy daemaruLevenshtein( - before, beforeLength, after, afterLength); - return static_cast<int>(EditDistance::getEditDistance(&daemaruLevenshtein)); -} - - -// In dictionary.cpp, getSuggestion() method, -// When USE_SUGGEST_INTERFACE_FOR_TYPING is true: -// -// // TODO: Revise the following logic thoroughly by referring to the logic -// // marked as "Otherwise" below. -// SUGGEST_INTERFACE_OUTPUT_SCALE was multiplied to the original suggestion scores to convert -// them to integers. -// score = (int)((original score) * SUGGEST_INTERFACE_OUTPUT_SCALE) -// Undo the scaling here to recover the original score. -// normalizedScore = ((float)score) / SUGGEST_INTERFACE_OUTPUT_SCALE -// -// Otherwise: suggestion scores are computed using the below formula. -// original score -// := powf(mTypedLetterMultiplier (this is defined 2), -// (the number of matched characters between typed word and suggested word)) -// * (individual word's score which defined in the unigram dictionary, -// and this score is defined in range [0, 255].) -// Then, the following processing is applied. -// - If the dictionary word is matched up to the point of the user entry -// (full match up to min(before.length(), after.length()) -// => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) -// - If the word is a true full match except for differences in accents or -// capitalization, then treat it as if the score was 255. -// - If before.length() == after.length() -// => multiply by mFullWordMultiplier (this is defined 2)) -// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2 -// For historical reasons we ignore the 1.2 modifier (because the measure for a good -// autocorrection threshold was done at a time when it didn't exist). This doesn't change -// the result. -// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2. - -/* static */ 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.0f; - } - const int distance = editDistance(before, beforeLength, after, afterLength); - int spaceCount = 0; - for (int i = 0; i < afterLength; ++i) { - if (after[i] == KEYCODE_SPACE) { - ++spaceCount; - } - } - - if (spaceCount == afterLength) { - return 0.0f; - } - - // add a weight based on edit distance. - // distance <= max(afterLength, beforeLength) == afterLength, - // so, 0 <= distance / afterLength <= 1 - const float weight = 1.0f - static_cast<float>(distance) / static_cast<float>(afterLength); - - // TODO: Revise the following logic thoroughly by referring to... - if (true /* USE_SUGGEST_INTERFACE_FOR_TYPING */) { - return (static_cast<float>(score) / SUGGEST_INTERFACE_OUTPUT_SCALE) * weight; - } - // ...this logic. - const float maxScore = score >= S_INT_MAX ? static_cast<float>(S_INT_MAX) - : static_cast<float>(MAX_INITIAL_SCORE) - * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER), - static_cast<float>(min(beforeLength, afterLength - spaceCount))) - * static_cast<float>(FULL_WORD_MULTIPLIER); - - return (static_cast<float>(score) / maxScore) * weight; -} -} // namespace latinime diff --git a/native/jni/src/obsolete/correction.h b/native/jni/src/obsolete/correction.h deleted file mode 100644 index 47dcef2d7..000000000 --- a/native/jni/src/obsolete/correction.h +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef LATINIME_CORRECTION_H -#define LATINIME_CORRECTION_H - -#include <cstring> // for memset() - -#include "defines.h" -#include "obsolete/correction_state.h" -#include "suggest/core/layout/proximity_info_state.h" -#include "utils/char_utils.h" - -namespace latinime { - -class ProximityInfo; - -class Correction { - public: - typedef enum { - TRAVERSE_ALL_ON_TERMINAL, - TRAVERSE_ALL_NOT_ON_TERMINAL, - UNRELATED, - ON_TERMINAL, - NOT_ON_TERMINAL - } CorrectionType; - - Correction() - : mProximityInfo(0), mUseFullEditDistance(false), mDoAutoCompletion(false), - mMaxEditDistance(0), mMaxDepth(0), mInputSize(0), mSpaceProximityPos(0), - mMissingSpacePos(0), mTerminalInputIndex(0), mTerminalOutputIndex(0), mMaxErrors(0), - mTotalTraverseCount(0), mNeedsToTraverseAllNodes(false), mOutputIndex(0), - mInputIndex(0), mEquivalentCharCount(0), mProximityCount(0), mExcessiveCount(0), - mTransposedCount(0), mSkippedCount(0), mTransposedPos(0), mExcessivePos(0), - mSkipPos(0), mLastCharExceeded(false), mMatching(false), mProximityMatching(false), - mAdditionalProximityMatching(false), mExceeding(false), mTransposing(false), - mSkipping(false), mProximityInfoState() { - memset(mWord, 0, sizeof(mWord)); - memset(mDistances, 0, sizeof(mDistances)); - memset(mEditDistanceTable, 0, sizeof(mEditDistanceTable)); - // NOTE: mCorrectionStates is an array of instances. - // No need to initialize it explicitly here. - } - - // Non virtual inline destructor -- never inherit this class - ~Correction() {} - void resetCorrection(); - void initCorrection(const ProximityInfo *pi, const int inputSize, const int maxDepth); - void initCorrectionState(const int rootPos, const int childCount, const bool traverseAll); - - // TODO: remove - void setCorrectionParams(const int skipPos, const int excessivePos, const int transposedPos, - const int spaceProximityPos, const int missingSpacePos, const bool useFullEditDistance, - const bool doAutoCompletion, const int maxErrors); - void checkState() const; - bool sameAsTyped() const; - bool initProcessState(const int index); - - int getInputIndex() const; - - bool needsToPrune() const; - - int pushAndGetTotalTraverseCount() { - return ++mTotalTraverseCount; - } - - int getFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, - const int wordCount, const bool isSpaceProximity, const int *word) const; - int getFinalProbability(const int probability, int **word, int *wordLength); - int getFinalProbabilityForSubQueue(const int probability, int **word, int *wordLength, - const int inputSize); - - CorrectionType processCharAndCalcState(const int c, const bool isTerminal); - - ///////////////////////// - // Tree helper methods - int goDownTree(const int parentIndex, const int childCount, const int firstChildPos); - - inline int getTreeSiblingPos(const int index) const { - return mCorrectionStates[index].mSiblingPos; - } - - inline void setTreeSiblingPos(const int index, const int pos) { - mCorrectionStates[index].mSiblingPos = pos; - } - - inline int getTreeParentIndex(const int index) const { - return mCorrectionStates[index].mParentIndex; - } - - class RankingAlgorithm { - public: - static int calculateFinalProbability(const int inputIndex, const int depth, - const int probability, int *editDistanceTable, const Correction *correction, - const int inputSize); - static int calcFreqForSplitMultipleWords(const int *freqArray, const int *wordLengthArray, - const int wordCount, const Correction *correction, const bool isSpaceProximity, - 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 MAX_INITIAL_SCORE = 255; - }; - - // proximity info state - void initInputParams(const ProximityInfo *proximityInfo, const int *inputCodes, - const int inputSize, const int *xCoordinates, const int *yCoordinates) { - mProximityInfoState.initInputParams(0, static_cast<float>(MAX_VALUE_FOR_WEIGHTING), - proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false); - } - - const int *getPrimaryInputWord() const { - return mProximityInfoState.getPrimaryInputWord(); - } - - int getPrimaryCodePointAt(const int index) const { - return mProximityInfoState.getPrimaryCodePointAt(index); - } - - private: - DISALLOW_COPY_AND_ASSIGN(Correction); - - // The following "rate"s are used as a multiplier before dividing by 100, so they are in - // percent. - static const int WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE; - static const int WORDS_WITH_MISSING_CHARACTER_DEMOTION_START_POS_10X; - static const int WORDS_WITH_MISSING_SPACE_CHARACTER_DEMOTION_RATE; - static const int WORDS_WITH_MISTYPED_SPACE_DEMOTION_RATE; - static const int WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE; - static const int WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE; - static const int WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE; - static const int FULL_MATCHED_WORDS_PROMOTION_RATE; - static const int WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE; - static const int WORDS_WITH_ADDITIONAL_PROXIMITY_CHARACTER_DEMOTION_RATE; - static const int WORDS_WITH_MATCH_SKIP_PROMOTION_RATE; - static const int WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_RATE; - static const int WORDS_WITH_JUST_ONE_CORRECTION_PROMOTION_MULTIPLIER; - static const int CORRECTION_COUNT_RATE_DEMOTION_RATE_BASE; - static const int INPUT_EXCEEDS_OUTPUT_DEMOTION_RATE; - static const int FIRST_CHAR_DIFFERENT_DEMOTION_RATE; - static const int TWO_WORDS_CAPITALIZED_DEMOTION_RATE; - static const int TWO_WORDS_CORRECTION_DEMOTION_BASE; - - ///////////////////////// - // static inline utils // - ///////////////////////// - static const int TWO_31ST_DIV_255 = S_INT_MAX / 255; - static inline int capped255MultForFullMatchAccentsOrCapitalizationDifference(const int num) { - return (num < TWO_31ST_DIV_255 ? 255 * num : S_INT_MAX); - } - - static const int TWO_31ST_DIV_2 = S_INT_MAX / 2; - 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 - if (multiplier < 0) { - if (DEBUG_DICT) { - ASSERT(false); - } - AKLOGI("--- Invalid multiplier: %d", multiplier); - } else if (multiplier == 0) { - *base = 0; - } else if (multiplier == 2) { - *base = TWO_31ST_DIV_2 >= temp ? temp << 1 : S_INT_MAX; - } else { - // TODO: This overflow check gives a wrong answer when, for example, - // temp = 2^16 + 1 and multiplier = 2^17 + 1. - // Fix this behavior. - const int tempRetval = temp * multiplier; - *base = tempRetval >= temp ? tempRetval : S_INT_MAX; - } - } - } - - 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; - } - int ret = base; - for (int i = 1; i < n; ++i) multiplyIntCapped(base, &ret); - return ret; - } - - AK_FORCE_INLINE static void multiplyRate(const int rate, int *freq) { - if (*freq != S_INT_MAX) { - if (*freq > 1000000) { - *freq /= 100; - multiplyIntCapped(rate, freq); - } else { - multiplyIntCapped(rate, freq); - *freq /= 100; - } - } - } - - inline int getSpaceProximityPos() const { - return mSpaceProximityPos; - } - inline int getMissingSpacePos() const { - return mMissingSpacePos; - } - - inline int getSkipPos() const { - return mSkipPos; - } - - inline int getExcessivePos() const { - return mExcessivePos; - } - - inline int getTransposedPos() const { - return mTransposedPos; - } - - inline void incrementInputIndex(); - inline void incrementOutputIndex(); - inline void startToTraverseAllNodes(); - 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 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; - const ProximityInfo *mProximityInfo; - - bool mUseFullEditDistance; - bool mDoAutoCompletion; - int mMaxEditDistance; - int mMaxDepth; - int mInputSize; - int mSpaceProximityPos; - int mMissingSpacePos; - int mTerminalInputIndex; - int mTerminalOutputIndex; - int mMaxErrors; - - int mTotalTraverseCount; - - // The following arrays are state buffer. - int mWord[MAX_WORD_LENGTH]; - int mDistances[MAX_WORD_LENGTH]; - - // Edit distance calculation requires a buffer with (N+1)^2 length for the input length N. - // Caveat: Do not create multiple tables per thread as this table eats up RAM a lot. - int mEditDistanceTable[(MAX_WORD_LENGTH + 1) * (MAX_WORD_LENGTH + 1)]; - - CorrectionState mCorrectionStates[MAX_WORD_LENGTH]; - - // The following member variables are being used as cache values of the correction state. - bool mNeedsToTraverseAllNodes; - int mOutputIndex; - int mInputIndex; - - int mEquivalentCharCount; - int mProximityCount; - int mExcessiveCount; - int mTransposedCount; - int mSkippedCount; - - int mTransposedPos; - int mExcessivePos; - int mSkipPos; - - bool mLastCharExceeded; - - bool mMatching; - bool mProximityMatching; - bool mAdditionalProximityMatching; - bool mExceeding; - bool mTransposing; - 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; -} - -AK_FORCE_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; - incrementOutputIndex(); - if (mNeedsToTraverseAllNodes && isTerminal) { - return TRAVERSE_ALL_ON_TERMINAL; - } - 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] 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 = CharUtils::toBaseLowerCase(output[outputLength - 1]); - const int prevCO = outputLength >= 2 ? CharUtils::toBaseLowerCase(output[outputLength - 2]) : 0; - for (int i = 1; i <= inputSize; ++i) { - const int ci = CharUtils::toBaseLowerCase(input[i - 1]); - const int 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 == CharUtils::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/obsolete/correction_state.h b/native/jni/src/obsolete/correction_state.h deleted file mode 100644 index a63d4aa94..000000000 --- a/native/jni/src/obsolete/correction_state.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef LATINIME_CORRECTION_STATE_H -#define LATINIME_CORRECTION_STATE_H - -#include <stdint.h> - -#include "defines.h" - -namespace latinime { - -struct CorrectionState { - int mParentIndex; - int mSiblingPos; - uint16_t mChildCount; - uint8_t mInputIndex; - - uint8_t mEquivalentCharCount; - uint8_t mProximityCount; - uint8_t mTransposedCount; - uint8_t mExcessiveCount; - uint8_t mSkippedCount; - - int8_t mTransposedPos; - int8_t mExcessivePos; - int8_t mSkipPos; // should be signed - - // TODO: int? - bool mLastCharExceeded; - - bool mMatching; - bool mTransposing; - bool mExceeding; - bool mSkipping; - bool mProximityMatching; - bool mAdditionalProximityMatching; - - bool mNeedsToTraverseAllNodes; -}; - -inline static void initCorrectionState(CorrectionState *state, const int rootPos, - const uint16_t childCount, const bool traverseAll) { - state->mParentIndex = -1; - state->mChildCount = childCount; - state->mInputIndex = 0; - state->mSiblingPos = rootPos; - state->mNeedsToTraverseAllNodes = traverseAll; - - state->mTransposedPos = -1; - state->mExcessivePos = -1; - state->mSkipPos = -1; - - state->mEquivalentCharCount = 0; - state->mProximityCount = 0; - state->mTransposedCount = 0; - state->mExcessiveCount = 0; - state->mSkippedCount = 0; - - state->mLastCharExceeded = false; - - state->mMatching = false; - state->mProximityMatching = false; - state->mTransposing = false; - state->mExceeding = false; - state->mSkipping = false; - state->mAdditionalProximityMatching = false; -} -} // namespace latinime -#endif // LATINIME_CORRECTION_STATE_H diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp index 59d1b19b6..53e2df62d 100644 --- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp +++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.cpp @@ -23,7 +23,6 @@ #include "defines.h" #include "suggest/core/dictionary/binary_dictionary_info.h" #include "suggest/core/dictionary/binary_format.h" -#include "suggest/core/dictionary/bloom_filter.h" #include "suggest/core/dictionary/dictionary.h" #include "suggest/core/dictionary/probability_utils.h" #include "utils/char_utils.h" @@ -170,30 +169,6 @@ int BigramDictionary::getBigramListPositionForWord(const int *prevWord, const in return pos; } -void BigramDictionary::fillBigramAddressToProbabilityMapAndFilter(const int *prevWord, - const int prevWordLength, std::map<int, int> *map, uint8_t *filter) const { - memset(filter, 0, BIGRAM_FILTER_BYTE_SIZE); - const uint8_t *const root = mBinaryDictionaryInfo->getDictRoot(); - int pos = getBigramListPositionForWord(prevWord, prevWordLength, - false /* forceLowerCaseSearch */); - if (0 == pos) { - // If no bigrams for this exact string, search again in lower case. - pos = getBigramListPositionForWord(prevWord, prevWordLength, - true /* forceLowerCaseSearch */); - } - if (0 == pos) return; - - uint8_t bigramFlags; - do { - bigramFlags = BinaryFormat::getFlagsAndForwardPointer(root, &pos); - const int probability = BinaryFormat::MASK_ATTRIBUTE_PROBABILITY & bigramFlags; - const int bigramPos = BinaryFormat::getAttributeAddressAndForwardPointer(root, bigramFlags, - &pos); - (*map)[bigramPos] = probability; - setInFilter(filter, bigramPos); - } while (BinaryFormat::FLAG_ATTRIBUTE_HAS_NEXT & bigramFlags); -} - bool BigramDictionary::checkFirstCharacter(int *word, int *inputCodePoints) const { // Checks whether this word starts with same character or neighboring characters of // what user typed. diff --git a/native/jni/src/suggest/core/dictionary/bigram_dictionary.h b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h index 8b7a253a2..06d0e9da3 100644 --- a/native/jni/src/suggest/core/dictionary/bigram_dictionary.h +++ b/native/jni/src/suggest/core/dictionary/bigram_dictionary.h @@ -17,9 +17,6 @@ #ifndef LATINIME_BIGRAM_DICTIONARY_H #define LATINIME_BIGRAM_DICTIONARY_H -#include <map> -#include <stdint.h> - #include "defines.h" namespace latinime { @@ -32,10 +29,9 @@ class BigramDictionary { int getBigrams(const int *word, int length, int *inputCodePoints, int inputSize, int *outWords, int *frequencies, int *outputTypes) const; - void fillBigramAddressToProbabilityMapAndFilter(const int *prevWord, const int prevWordLength, - std::map<int, int> *map, uint8_t *filter) const; bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const; ~BigramDictionary(); + private: DISALLOW_IMPLICIT_CONSTRUCTORS(BigramDictionary); diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp index 50e0211d7..737df63c7 100644 --- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format.cpp +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "suggest/core/dictionary/binary_dictionary_format.h" +#include "suggest/core/dictionary/binary_dictionary_format_utils.h" namespace latinime { @@ -31,7 +31,6 @@ const int BinaryDictionaryFormat::DICTIONARY_MINIMUM_SIZE = 4; // 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 uint32_t BinaryDictionaryFormat::FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100; -const int BinaryDictionaryFormat::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 @@ -39,9 +38,6 @@ const int BinaryDictionaryFormat::FORMAT_VERSION_1_HEADER_SIZE = 5; const uint32_t BinaryDictionaryFormat::FORMAT_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE; // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12 const int BinaryDictionaryFormat::FORMAT_VERSION_2_MINIMUM_SIZE = 12; -const int BinaryDictionaryFormat::VERSION_2_MAGIC_NUMBER_SIZE = 4; -const int BinaryDictionaryFormat::VERSION_2_DICTIONARY_VERSION_SIZE = 2; -const int BinaryDictionaryFormat::VERSION_2_DICTIONARY_FLAG_SIZE = 2; /* static */ BinaryDictionaryFormat::FORMAT_VERSION BinaryDictionaryFormat::detectFormatVersion( const uint8_t *const dict, const int dictSize) { diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h index 3aa1662da..c0fd56111 100644 --- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format.h +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef LATINIME_BINARY_DICTIONARY_FORMAT_H -#define LATINIME_BINARY_DICTIONARY_FORMAT_H +#ifndef LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H +#define LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H #include <stdint.h> @@ -42,30 +42,13 @@ class BinaryDictionaryFormat { static FORMAT_VERSION detectFormatVersion(const uint8_t *const dict, const int dictSize); - static AK_FORCE_INLINE int getHeaderSize( - const uint8_t *const dict, const FORMAT_VERSION format) { - switch (format) { - case VERSION_1: - return FORMAT_VERSION_1_HEADER_SIZE; - case VERSION_2: - // See the format of the header in the comment in detectFormat() above - return ByteArrayUtils::readUint32(dict, 8); - default: - return S_INT_MAX; - } - } - private: DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryFormat); static const int DICTIONARY_MINIMUM_SIZE; static const uint32_t FORMAT_VERSION_1_MAGIC_NUMBER; - static const int FORMAT_VERSION_1_HEADER_SIZE; static const uint32_t FORMAT_VERSION_2_MAGIC_NUMBER; static const int FORMAT_VERSION_2_MINIMUM_SIZE; - static const int VERSION_2_MAGIC_NUMBER_SIZE; - static const int VERSION_2_DICTIONARY_VERSION_SIZE ; - static const int VERSION_2_DICTIONARY_FLAG_SIZE; }; } // namespace latinime -#endif /* LATINIME_BINARY_DICTIONARY_FORMAT_H */ +#endif /* LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H */ diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp new file mode 100644 index 000000000..04bb81f71 --- /dev/null +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013, 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 "suggest/core/dictionary/binary_dictionary_header.h" + +#include "defines.h" +#include "suggest/core/dictionary/binary_dictionary_info.h" + +namespace latinime { + +const char *const BinaryDictionaryHeader::MULTIPLE_WORDS_DEMOTION_RATE_KEY = + "MULTIPLE_WORDS_DEMOTION_RATE"; +const float BinaryDictionaryHeader::DEFAULT_MULTI_WORD_COST_MULTIPLIER = 1.0f; +const float BinaryDictionaryHeader::MULTI_WORD_COST_MULTIPLIER_SCALE = 100.0f; + +BinaryDictionaryHeader::BinaryDictionaryHeader( + const BinaryDictionaryInfo *const binaryDictionaryInfo) + : mBinaryDictionaryInfo(binaryDictionaryInfo), + mDictionaryFlags(BinaryDictionaryHeaderReader::getFlags(binaryDictionaryInfo)), + mSize(BinaryDictionaryHeaderReader::getHeaderSize(binaryDictionaryInfo)), + mMultiWordCostMultiplier(readMultiWordCostMultiplier()) {} + +float BinaryDictionaryHeader::readMultiWordCostMultiplier() const { + const int headerValue = BinaryDictionaryHeaderReader::readHeaderValueInt( + mBinaryDictionaryInfo, MULTIPLE_WORDS_DEMOTION_RATE_KEY); + if (headerValue == S_INT_MIN) { + // not found + return DEFAULT_MULTI_WORD_COST_MULTIPLIER; + } + if (headerValue <= 0) { + return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); + } + return MULTI_WORD_COST_MULTIPLIER_SCALE / static_cast<float>(headerValue); +} + +} // namespace latinime diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h new file mode 100644 index 000000000..9db000362 --- /dev/null +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_BINARY_DICTIONARY_HEADER_H +#define LATINIME_BINARY_DICTIONARY_HEADER_H + +#include "defines.h" +#include "suggest/core/dictionary/binary_dictionary_header_reading_utils.h" + +namespace latinime { + +class BinaryDictionaryInfo; + +/** + * This class abstracts dictionary header structures and provide interface to access dictionary + * header information. + */ +class BinaryDictionaryHeader { + public: + explicit BinaryDictionaryHeader(const BinaryDictionaryInfo *const binaryDictionaryInfo); + + AK_FORCE_INLINE int getSize() const { + return mSize; + } + + AK_FORCE_INLINE bool supportsDynamicUpdate() const { + return BinaryDictionaryHeaderReader::supportsDynamicUpdate(mDictionaryFlags); + } + + AK_FORCE_INLINE bool requiresGermanUmlautProcessing() const { + return BinaryDictionaryHeaderReader::requiresGermanUmlautProcessing(mDictionaryFlags); + } + + AK_FORCE_INLINE bool requiresFrenchLigatureProcessing() const { + return BinaryDictionaryHeaderReader::requiresFrenchLigatureProcessing(mDictionaryFlags); + } + + AK_FORCE_INLINE float getMultiWordCostMultiplier() const { + return mMultiWordCostMultiplier; + } + + private: + DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryHeader); + + static const char *const MULTIPLE_WORDS_DEMOTION_RATE_KEY; + static const float DEFAULT_MULTI_WORD_COST_MULTIPLIER; + static const float MULTI_WORD_COST_MULTIPLIER_SCALE; + + const BinaryDictionaryInfo *const mBinaryDictionaryInfo; + const BinaryDictionaryHeaderReader::DictionaryFlags mDictionaryFlags; + const int mSize; + const float mMultiWordCostMultiplier; + + float readMultiWordCostMultiplier() const; +}; +} // namespace latinime +#endif // LATINIME_BINARY_DICTIONARY_HEADER_H diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp new file mode 100644 index 000000000..c09a78f03 --- /dev/null +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2013, 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 "suggest/core/dictionary/binary_dictionary_header_reading_utils.h" + +#include <cctype> +#include <cstdlib> + +#include "defines.h" +#include "suggest/core/dictionary/binary_dictionary_info.h" + +namespace latinime { + +const int BinaryDictionaryHeaderReader::MAX_OPTION_KEY_LENGTH = 256; + +const int BinaryDictionaryHeaderReader::FORMAT_VERSION_1_HEADER_SIZE = 5; + +const int BinaryDictionaryHeaderReader::VERSION_2_MAGIC_NUMBER_SIZE = 4; +const int BinaryDictionaryHeaderReader::VERSION_2_DICTIONARY_VERSION_SIZE = 2; +const int BinaryDictionaryHeaderReader::VERSION_2_DICTIONARY_FLAG_SIZE = 2; +const int BinaryDictionaryHeaderReader::VERSION_2_DICTIONARY_HEADER_SIZE_SIZE = 4; + +const BinaryDictionaryHeaderReader::DictionaryFlags BinaryDictionaryHeaderReader::NO_FLAGS = 0; +// Flags for special processing +// Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or +// something very bad (like, the apocalypse) will happen. Please update both at the same time. +const BinaryDictionaryHeaderReader::DictionaryFlags + BinaryDictionaryHeaderReader::GERMAN_UMLAUT_PROCESSING_FLAG = 0x1; +const BinaryDictionaryHeaderReader::DictionaryFlags + BinaryDictionaryHeaderReader::SUPPORTS_DYNAMIC_UPDATE_FLAG = 0x2; +const BinaryDictionaryHeaderReader::DictionaryFlags + BinaryDictionaryHeaderReader::FRENCH_LIGATURE_PROCESSING_FLAG = 0x4; + +/* static */ int BinaryDictionaryHeaderReader::getHeaderSize( + const BinaryDictionaryInfo *const binaryDictionaryInfo) { + switch (binaryDictionaryInfo->getFormat()) { + case BinaryDictionaryFormat::VERSION_1: + return FORMAT_VERSION_1_HEADER_SIZE; + case BinaryDictionaryFormat::VERSION_2: + // See the format of the header in the comment in + // BinaryDictionaryFormatUtils::detectFormatVersion() + return ByteArrayUtils::readUint32(binaryDictionaryInfo->getDictBuf(), + VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE + + VERSION_2_DICTIONARY_FLAG_SIZE); + default: + return S_INT_MAX; + } +} + +/* static */ BinaryDictionaryHeaderReader::DictionaryFlags BinaryDictionaryHeaderReader::getFlags( + const BinaryDictionaryInfo *const binaryDictionaryInfo) { + switch (binaryDictionaryInfo->getFormat()) { + case BinaryDictionaryFormat::VERSION_1: + return NO_FLAGS; + case BinaryDictionaryFormat::VERSION_2: + return ByteArrayUtils::readUint16(binaryDictionaryInfo->getDictBuf(), + VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE); + default: + return NO_FLAGS; + } +} + +// Returns if the key is found or not and reads the found value into outValue. +/* static */ bool BinaryDictionaryHeaderReader::readHeaderValue( + const BinaryDictionaryInfo *const binaryDictionaryInfo, + const char *const key, int *outValue, const int outValueSize) { + if (outValueSize <= 0 || !hasHeaderAttributes(binaryDictionaryInfo->getFormat())) { + return false; + } + const int headerSize = getHeaderSize(binaryDictionaryInfo); + int pos = getHeaderOptionsPosition(binaryDictionaryInfo->getFormat()); + while (pos < headerSize) { + if(ByteArrayUtils::compareStringInBufferWithCharArray( + binaryDictionaryInfo->getDictBuf(), key, headerSize - pos, &pos) == 0) { + // The key was found. + ByteArrayUtils::readStringAndAdvancePosition( + binaryDictionaryInfo->getDictBuf(), outValueSize, outValue, &pos); + return true; + } + ByteArrayUtils::advancePositionToBehindString( + binaryDictionaryInfo->getDictBuf(), headerSize - pos, &pos); + } + // The key was not found. + return false; +} + +/* static */ int BinaryDictionaryHeaderReader::readHeaderValueInt( + const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key) { + const int bufferSize = LARGEST_INT_DIGIT_COUNT; + int intBuffer[bufferSize]; + char charBuffer[bufferSize]; + if (!readHeaderValue(binaryDictionaryInfo, key, intBuffer, bufferSize)) { + return S_INT_MIN; + } + for (int i = 0; i < bufferSize; ++i) { + charBuffer[i] = intBuffer[i]; + if (charBuffer[i] == '0') { + break; + } + if (!isdigit(charBuffer[i])) { + // If not a number, return S_INT_MIN + return S_INT_MIN; + } + } + return atoi(charBuffer); +} + +} // namespace latinime diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h new file mode 100644 index 000000000..6e9dca73c --- /dev/null +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_DICTIONARY_HEADER_READING_UTILS_H +#define LATINIME_DICTIONARY_HEADER_READING_UTILS_H + +#include <stdint.h> + +#include "defines.h" +#include "suggest/core/dictionary/binary_dictionary_format_utils.h" + +namespace latinime { + +class BinaryDictionaryInfo; + +class BinaryDictionaryHeaderReader { + public: + typedef uint16_t DictionaryFlags; + + static const int MAX_OPTION_KEY_LENGTH; + + static int getHeaderSize(const BinaryDictionaryInfo *const binaryDictionaryInfo); + + static DictionaryFlags getFlags(const BinaryDictionaryInfo *const binaryDictionaryInfo); + + static AK_FORCE_INLINE bool supportsDynamicUpdate(const DictionaryFlags flags) { + return (flags & SUPPORTS_DYNAMIC_UPDATE_FLAG) != 0; + } + + static AK_FORCE_INLINE bool requiresGermanUmlautProcessing(const DictionaryFlags flags) { + return (flags & GERMAN_UMLAUT_PROCESSING_FLAG) != 0; + } + + static AK_FORCE_INLINE bool requiresFrenchLigatureProcessing(const DictionaryFlags flags) { + return (flags & FRENCH_LIGATURE_PROCESSING_FLAG) != 0; + } + + static AK_FORCE_INLINE bool hasHeaderAttributes( + const BinaryDictionaryFormat::FORMAT_VERSION format) { + // Only format 2 and above have header attributes as {key,value} string pairs. + switch (format) { + case BinaryDictionaryFormat::VERSION_2: + return true; + break; + default: + return false; + } + } + + static AK_FORCE_INLINE int getHeaderOptionsPosition( + const BinaryDictionaryFormat::FORMAT_VERSION format) { + switch (format) { + case BinaryDictionaryFormat::VERSION_2: + return VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE + + VERSION_2_DICTIONARY_FLAG_SIZE + VERSION_2_DICTIONARY_HEADER_SIZE_SIZE; + break; + default: + return 0; + } + } + + static bool readHeaderValue( + const BinaryDictionaryInfo *const binaryDictionaryInfo, + const char *const key, int *outValue, const int outValueSize); + + static int readHeaderValueInt( + const BinaryDictionaryInfo *const binaryDictionaryInfo, const char *const key); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryHeaderReader); + + static const int FORMAT_VERSION_1_HEADER_SIZE; + + static const int VERSION_2_MAGIC_NUMBER_SIZE; + static const int VERSION_2_DICTIONARY_VERSION_SIZE; + static const int VERSION_2_DICTIONARY_FLAG_SIZE; + static const int VERSION_2_DICTIONARY_HEADER_SIZE_SIZE; + + static const DictionaryFlags NO_FLAGS; + // Flags for special processing + // Those *must* match the flags in makedict (FormatSpec#*_PROCESSING_FLAGS) or + // something very bad (like, the apocalypse) will happen. Please update both at the same time. + static const DictionaryFlags GERMAN_UMLAUT_PROCESSING_FLAG; + static const DictionaryFlags SUPPORTS_DYNAMIC_UPDATE_FLAG; + static const DictionaryFlags FRENCH_LIGATURE_PROCESSING_FLAG; + static const DictionaryFlags CONTAINS_BIGRAMS_FLAG; +}; +} +#endif /* LATINIME_DICTIONARY_HEADER_READING_UTILS_H */ diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h index 8508c6786..0b77e5ee9 100644 --- a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h +++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h @@ -20,16 +20,19 @@ #include <stdint.h> #include "defines.h" -#include "suggest/core/dictionary/binary_dictionary_format.h" +#include "suggest/core/dictionary/binary_dictionary_format_utils.h" +#include "suggest/core/dictionary/binary_dictionary_header.h" namespace latinime { +class BinaryDictionaryHeader; + class BinaryDictionaryInfo { public: BinaryDictionaryInfo(const uint8_t *const dictBuf, const int dictSize) : mDictBuf(dictBuf), - mFormat(BinaryDictionaryFormat::detectFormatVersion(mDictBuf, dictSize)), - mDictRoot(mDictBuf + BinaryDictionaryFormat::getHeaderSize(mDictBuf, mFormat)) {} + mDictionaryFormat(BinaryDictionaryFormat::detectFormatVersion(mDictBuf, dictSize)), + mDictionaryHeader(this), mDictRoot(mDictBuf + mDictionaryHeader.getSize()) {} AK_FORCE_INLINE const uint8_t *getDictBuf() const { return mDictBuf; @@ -40,18 +43,23 @@ class BinaryDictionaryInfo { } AK_FORCE_INLINE BinaryDictionaryFormat::FORMAT_VERSION getFormat() const { - return mFormat; + return mDictionaryFormat; } AK_FORCE_INLINE int getRootPosition() const { return 0; } + AK_FORCE_INLINE const BinaryDictionaryHeader *getHeader() const { + return &mDictionaryHeader; + } + private: DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryInfo); const uint8_t *const mDictBuf; - const BinaryDictionaryFormat::FORMAT_VERSION mFormat; + const BinaryDictionaryFormat::FORMAT_VERSION mDictionaryFormat; + const BinaryDictionaryHeader mDictionaryHeader; const uint8_t *const mDictRoot; }; } diff --git a/native/jni/src/suggest/core/dictionary/binary_format.h b/native/jni/src/suggest/core/dictionary/binary_format.h index c82065f97..0a290d80a 100644 --- a/native/jni/src/suggest/core/dictionary/binary_format.h +++ b/native/jni/src/suggest/core/dictionary/binary_format.h @@ -17,10 +17,8 @@ #ifndef LATINIME_BINARY_FORMAT_H #define LATINIME_BINARY_FORMAT_H -#include <cstdlib> #include <stdint.h> -#include "suggest/core/dictionary/bloom_filter.h" #include "suggest/core/dictionary/probability_utils.h" #include "utils/char_utils.h" #include "utils/hash_map_compat.h" @@ -61,17 +59,9 @@ class BinaryFormat { // Mask and flags for attribute address type selection. static const int MASK_ATTRIBUTE_ADDRESS_TYPE = 0x30; - static const int UNKNOWN_FORMAT = -1; static const int SHORTCUT_LIST_SIZE_SIZE = 2; - static int detectFormat(const uint8_t *const dict, const int dictSize); - static int getHeaderSize(const uint8_t *const dict, const int dictSize); - static int getFlags(const uint8_t *const dict, const int dictSize); static bool hasBlacklistedOrNotAWordFlag(const int flags); - static void readHeaderValue(const uint8_t *const dict, const int dictSize, - const char *const key, int *outValue, const int outValueSize); - static int readHeaderValueInt(const uint8_t *const dict, const int dictSize, - const char *const key); static int getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos); static uint8_t getFlagsAndForwardPointer(const uint8_t *const dict, int *pos); static int getCodePointAndForwardPointer(const uint8_t *const dict, int *pos); @@ -93,20 +83,11 @@ class BinaryFormat { int *outWord, int *outUnigramProbability); static int getBigramProbabilityFromHashMap(const int position, const hash_map_compat<int, int> *bigramMap, const int unigramProbability); - static float getMultiWordCostMultiplier(const uint8_t *const dict, const int dictSize); static void fillBigramProbabilityToHashMap(const uint8_t *const root, int position, hash_map_compat<int, int> *bigramMap); static int getBigramProbability(const uint8_t *const root, int position, const int nextPosition, const int unigramProbability); - // Flags for special processing - // Those *must* match the flags in makedict (BinaryDictInputOutput#*_PROCESSING_FLAG) or - // something very bad (like, the apocalypse) will happen. Please update both at the same time. - enum { - REQUIRES_GERMAN_UMLAUT_PROCESSING = 0x1, - REQUIRES_FRENCH_LIGATURES_PROCESSING = 0x4 - }; - private: DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryFormat); static int getBigramListPositionForWordPosition(const uint8_t *const root, int position); @@ -119,20 +100,6 @@ class BinaryFormat { static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES = 0x20; static const int FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES = 0x30; - // Any file smaller than this is not a dictionary. - static const int DICTIONARY_MINIMUM_SIZE = 4; - // 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 int FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100; - static const 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 int FORMAT_VERSION_2_MAGIC_NUMBER = -1681835266; // 0x9BC13AFE - // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12 - static const int FORMAT_VERSION_2_MINIMUM_SIZE = 12; - static const int CHARACTER_ARRAY_TERMINATOR_SIZE = 1; static const int MINIMAL_ONE_BYTE_CHARACTER_VALUE = 0x20; static const int CHARACTER_ARRAY_TERMINATOR = 0x1F; @@ -142,122 +109,10 @@ class BinaryFormat { static int skipBigrams(const uint8_t *const dict, const uint8_t flags, const int pos); }; -AK_FORCE_INLINE int BinaryFormat::detectFormat(const uint8_t *const dict, const int dictSize) { - // The magic number is stored big-endian. - // If the dictionary is less than 4 bytes, we can't even read the magic number, so we don't - // understand this format. - if (dictSize < DICTIONARY_MINIMUM_SIZE) return UNKNOWN_FORMAT; - const int magicNumber = (dict[0] << 24) + (dict[1] << 16) + (dict[2] << 8) + dict[3]; - switch (magicNumber) { - case FORMAT_VERSION_1_MAGIC_NUMBER: - // Format 1 header is exactly 5 bytes long and looks like: - // Magic number (2 bytes) 0x78 0xB1 - // Version number (1 byte) 0x01 - // Options (2 bytes) must be 0x00 0x00 - return 1; - case FORMAT_VERSION_2_MAGIC_NUMBER: - // Version 2 dictionaries are at least 12 bytes long (see below details for the header). - // If this dictionary has the version 2 magic number but is less than 12 bytes long, then - // it's an unknown format and we need to avoid confidently reading the next bytes. - if (dictSize < FORMAT_VERSION_2_MINIMUM_SIZE) return UNKNOWN_FORMAT; - // Format 2 header is as follows: - // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE - // Version number (2 bytes) 0x00 0x02 - // Options (2 bytes) - // Header size (4 bytes) : integer, big endian - return (dict[4] << 8) + dict[5]; - default: - return UNKNOWN_FORMAT; - } -} - -inline int BinaryFormat::getFlags(const uint8_t *const dict, const int dictSize) { - switch (detectFormat(dict, dictSize)) { - case 1: - return NO_FLAGS; // TODO: NO_FLAGS is unused anywhere else? - default: - return (dict[6] << 8) + dict[7]; - } -} - inline bool BinaryFormat::hasBlacklistedOrNotAWordFlag(const int flags) { return (flags & (FLAG_IS_BLACKLISTED | FLAG_IS_NOT_A_WORD)) != 0; } -inline int BinaryFormat::getHeaderSize(const uint8_t *const dict, const int dictSize) { - switch (detectFormat(dict, dictSize)) { - case 1: - return FORMAT_VERSION_1_HEADER_SIZE; - case 2: - // See the format of the header in the comment in detectFormat() above - return (dict[8] << 24) + (dict[9] << 16) + (dict[10] << 8) + dict[11]; - default: - return S_INT_MAX; - } -} - -inline void BinaryFormat::readHeaderValue(const uint8_t *const dict, const int dictSize, - const char *const key, int *outValue, const int outValueSize) { - int outValueIndex = 0; - // Only format 2 and above have header attributes as {key,value} string pairs. For prior - // formats, we just return an empty string, as if the key wasn't found. - if (2 <= detectFormat(dict, dictSize)) { - const int headerOptionsOffset = 4 /* magic number */ - + 2 /* dictionary version */ + 2 /* flags */; - const int headerSize = - (dict[headerOptionsOffset] << 24) + (dict[headerOptionsOffset + 1] << 16) - + (dict[headerOptionsOffset + 2] << 8) + dict[headerOptionsOffset + 3]; - const int headerEnd = headerOptionsOffset + 4 + headerSize; - int index = headerOptionsOffset + 4; - while (index < headerEnd) { - int keyIndex = 0; - int codePoint = getCodePointAndForwardPointer(dict, &index); - while (codePoint != NOT_A_CODE_POINT) { - if (codePoint != key[keyIndex++]) { - break; - } - codePoint = getCodePointAndForwardPointer(dict, &index); - } - if (codePoint == NOT_A_CODE_POINT && key[keyIndex] == 0) { - // We found the key! Copy and return the value. - codePoint = getCodePointAndForwardPointer(dict, &index); - while (codePoint != NOT_A_CODE_POINT && outValueIndex < outValueSize) { - outValue[outValueIndex++] = codePoint; - codePoint = getCodePointAndForwardPointer(dict, &index); - } - // Finished copying. Break to go to the termination code. - break; - } - // We didn't find the key, skip the remainder of it and its value - while (codePoint != NOT_A_CODE_POINT) { - codePoint = getCodePointAndForwardPointer(dict, &index); - } - codePoint = getCodePointAndForwardPointer(dict, &index); - while (codePoint != NOT_A_CODE_POINT) { - codePoint = getCodePointAndForwardPointer(dict, &index); - } - } - // We couldn't find it - fall through and return an empty value. - } - // Put a terminator 0 if possible at all (always unless outValueSize is <= 0) - if (outValueIndex >= outValueSize) outValueIndex = outValueSize - 1; - if (outValueIndex >= 0) outValue[outValueIndex] = 0; -} - -inline int BinaryFormat::readHeaderValueInt(const uint8_t *const dict, const int dictSize, - const char *const key) { - const int bufferSize = LARGEST_INT_DIGIT_COUNT; - int intBuffer[bufferSize]; - char charBuffer[bufferSize]; - BinaryFormat::readHeaderValue(dict, dictSize, key, intBuffer, bufferSize); - for (int i = 0; i < bufferSize; ++i) { - charBuffer[i] = intBuffer[i]; - } - // If not a number, return S_INT_MIN - if (!isdigit(charBuffer[0])) return S_INT_MIN; - return atoi(charBuffer); -} - AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t *const dict, int *pos) { const int msb = dict[(*pos)++]; @@ -265,18 +120,6 @@ AK_FORCE_INLINE int BinaryFormat::getGroupCountAndForwardPointer(const uint8_t * return ((msb & 0x7F) << 8) | dict[(*pos)++]; } -inline float BinaryFormat::getMultiWordCostMultiplier(const uint8_t *const dict, - const int dictSize) { - const int headerValue = readHeaderValueInt(dict, dictSize, "MULTIPLE_WORDS_DEMOTION_RATE"); - if (headerValue == S_INT_MIN) { - return 1.0f; - } - if (headerValue <= 0) { - return static_cast<float>(MAX_VALUE_FOR_WEIGHTING); - } - return 100.0f / static_cast<float>(headerValue); -} - inline uint8_t BinaryFormat::getFlagsAndForwardPointer(const uint8_t *const dict, int *pos) { return dict[(*pos)++]; } diff --git a/native/jni/src/suggest/core/dictionary/byte_array_utils.h b/native/jni/src/suggest/core/dictionary/byte_array_utils.h index 832b74725..d3321f624 100644 --- a/native/jni/src/suggest/core/dictionary/byte_array_utils.h +++ b/native/jni/src/suggest/core/dictionary/byte_array_utils.h @@ -116,8 +116,8 @@ class ByteArrayUtils { * Reads code points until the terminator is found. */ // Returns the length of the string. - static int readStringAndAdvancePosition(const uint8_t *const buffer, int *const pos, - int *const outBuffer, const int maxLength) { + static int readStringAndAdvancePosition(const uint8_t *const buffer, + const int maxLength, int *const outBuffer, int *const pos) { int length = 0; int codePoint = readCodePointAndAdvancePosition(buffer, pos); while (NOT_A_CODE_POINT != codePoint && length < maxLength) { @@ -129,7 +129,7 @@ class ByteArrayUtils { // Advances the position and returns the length of the string. static int advancePositionToBehindString( - const uint8_t *const buffer, int *const pos, const int maxLength) { + const uint8_t *const buffer, const int maxLength, int *const pos) { int length = 0; int codePoint = readCodePointAndAdvancePosition(buffer, pos); while (NOT_A_CODE_POINT != codePoint && length < maxLength) { @@ -138,6 +138,39 @@ class ByteArrayUtils { return length; } + // Returns an integer less than, equal to, or greater than zero when string starting from pos + // in buffer is less than, match, or is greater than charArray. + static AK_FORCE_INLINE int compareStringInBufferWithCharArray(const uint8_t *const buffer, + const char *const charArray, const int maxLength, int *const pos) { + int index = 0; + int codePoint = readCodePointAndAdvancePosition(buffer, pos); + const uint8_t *const uint8CharArrayForComparison = + reinterpret_cast<const uint8_t *>(charArray); + while (NOT_A_CODE_POINT != codePoint + && '\0' != uint8CharArrayForComparison[index] && index < maxLength) { + if (codePoint != uint8CharArrayForComparison[index]) { + // Different character is found. + // Skip the rest of the string in the buffer. + advancePositionToBehindString(buffer, maxLength - index, pos); + return codePoint - uint8CharArrayForComparison[index]; + } + // Advance + codePoint = readCodePointAndAdvancePosition(buffer, pos); + ++index; + } + if (NOT_A_CODE_POINT != codePoint && index < maxLength) { + // Skip the rest of the string in the buffer. + advancePositionToBehindString(buffer, maxLength - index, pos); + } + if (NOT_A_CODE_POINT == codePoint && '\0' == uint8CharArrayForComparison[index]) { + // When both of the last characters are terminals, we consider the string in the buffer + // matches the given char array + return 0; + } else { + return codePoint - uint8CharArrayForComparison[index]; + } + } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(ByteArrayUtils); diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp index 2d4ad5df5..561e22d2d 100644 --- a/native/jni/src/suggest/core/dictionary/dictionary.cpp +++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp @@ -33,11 +33,10 @@ namespace latinime { Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufAdjust) - : mBinaryDicitonaryInfo(static_cast<const uint8_t *>(dict), dictSize), + : mBinaryDictionaryInfo(static_cast<const uint8_t *>(dict), dictSize), mDictSize(dictSize), - mDictFlags(BinaryFormat::getFlags(mBinaryDicitonaryInfo.getDictBuf(), dictSize)), mMmapFd(mmapFd), mDictBufAdjust(dictBufAdjust), - mBigramDictionary(new BigramDictionary(&mBinaryDicitonaryInfo)), + mBigramDictionary(new BigramDictionary(&mBinaryDictionaryInfo)), mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())), mTypingSuggest(new Suggest(TypingSuggestPolicyFactory::getTypingSuggestPolicy())) { } @@ -85,7 +84,7 @@ int Dictionary::getBigrams(const int *word, int length, int *inputCodePoints, in } int Dictionary::getProbability(const int *word, int length) const { - const uint8_t *const root = mBinaryDicitonaryInfo.getDictRoot(); + const uint8_t *const root = mBinaryDictionaryInfo.getDictRoot(); int pos = BinaryFormat::getTerminalPosition(root, word, length, false /* forceLowerCaseSearch */); if (NOT_VALID_WORD == pos) { @@ -112,8 +111,4 @@ bool Dictionary::isValidBigram(const int *word1, int length1, const int *word2, return mBigramDictionary->isValidBigram(word1, length1, word2, length2); } -int Dictionary::getDictFlags() const { - return mDictFlags; -} - } // namespace latinime diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h index 1f25080b1..151f26183 100644 --- a/native/jni/src/suggest/core/dictionary/dictionary.h +++ b/native/jni/src/suggest/core/dictionary/dictionary.h @@ -66,22 +66,20 @@ class Dictionary { int getProbability(const int *word, int length) const; bool isValidBigram(const int *word1, int length1, const int *word2, int length2) const; const BinaryDictionaryInfo *getBinaryDictionaryInfo() const { - return &mBinaryDicitonaryInfo; + return &mBinaryDictionaryInfo; } int getDictSize() const { return mDictSize; } int getMmapFd() const { return mMmapFd; } int getDictBufAdjust() const { return mDictBufAdjust; } - int getDictFlags() const; virtual ~Dictionary(); private: DISALLOW_IMPLICIT_CONSTRUCTORS(Dictionary); - const BinaryDictionaryInfo mBinaryDicitonaryInfo; + const BinaryDictionaryInfo mBinaryDictionaryInfo; // Used only for the mmap version of dictionary loading, but we use these as dummy variables // also for the malloc version. const int mDictSize; - const int mDictFlags; const int mMmapFd; const int mDictBufAdjust; diff --git a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp index f53e56ef1..af378b1b7 100644 --- a/native/jni/src/suggest/core/dictionary/digraph_utils.cpp +++ b/native/jni/src/suggest/core/dictionary/digraph_utils.cpp @@ -16,8 +16,10 @@ #include "suggest/core/dictionary/digraph_utils.h" +#include <cstdlib> + #include "defines.h" -#include "suggest/core/dictionary/binary_format.h" +#include "suggest/core/dictionary/binary_dictionary_header.h" #include "utils/char_utils.h" namespace latinime { @@ -33,8 +35,8 @@ const DigraphUtils::DigraphType DigraphUtils::USED_DIGRAPH_TYPES[] = { DIGRAPH_TYPE_GERMAN_UMLAUT, DIGRAPH_TYPE_FRENCH_LIGATURES }; /* static */ bool DigraphUtils::hasDigraphForCodePoint( - const int dictFlags, const int compositeGlyphCodePoint) { - const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(dictFlags); + const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint) { + const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(header); if (DigraphUtils::getDigraphForDigraphTypeAndCodePoint(digraphType, compositeGlyphCodePoint)) { return true; } @@ -43,24 +45,16 @@ const DigraphUtils::DigraphType DigraphUtils::USED_DIGRAPH_TYPES[] = // Returns the digraph type associated with the given dictionary. /* static */ DigraphUtils::DigraphType DigraphUtils::getDigraphTypeForDictionary( - const int dictFlags) { - if (BinaryFormat::REQUIRES_GERMAN_UMLAUT_PROCESSING & dictFlags) { + const BinaryDictionaryHeader *const header) { + if (header->requiresGermanUmlautProcessing()) { return DIGRAPH_TYPE_GERMAN_UMLAUT; } - if (BinaryFormat::REQUIRES_FRENCH_LIGATURES_PROCESSING & dictFlags) { + if (header->requiresFrenchLigatureProcessing()) { return DIGRAPH_TYPE_FRENCH_LIGATURES; } return DIGRAPH_TYPE_NONE; } -// Retrieves the set of all digraphs associated with the given dictionary flags. -// Returns the size of the digraph array, or 0 if none exist. -/* static */ int DigraphUtils::getAllDigraphsForDictionaryAndReturnSize( - const int dictFlags, const DigraphUtils::digraph_t **const digraphs) { - const DigraphUtils::DigraphType digraphType = getDigraphTypeForDictionary(dictFlags); - return getAllDigraphsForDigraphTypeAndReturnSize(digraphType, digraphs); -} - // Returns the digraph codepoint for the given composite glyph codepoint and digraph codepoint index // (which specifies the first or second codepoint in the digraph). /* static */ int DigraphUtils::getDigraphCodePointForIndex(const int compositeGlyphCodePoint, @@ -124,7 +118,7 @@ const DigraphUtils::DigraphType DigraphUtils::USED_DIGRAPH_TYPES[] = const DigraphUtils::digraph_t *digraphs = 0; const int compositeGlyphLowerCodePoint = CharUtils::toLowerCase(compositeGlyphCodePoint); const int digraphsSize = - DigraphUtils::getAllDigraphsForDictionaryAndReturnSize(digraphType, &digraphs); + DigraphUtils::getAllDigraphsForDigraphTypeAndReturnSize(digraphType, &digraphs); for (int i = 0; i < digraphsSize; i++) { if (digraphs[i].compositeGlyph == compositeGlyphLowerCodePoint) { return &digraphs[i]; diff --git a/native/jni/src/suggest/core/dictionary/digraph_utils.h b/native/jni/src/suggest/core/dictionary/digraph_utils.h index c1205940c..9d74fe3a6 100644 --- a/native/jni/src/suggest/core/dictionary/digraph_utils.h +++ b/native/jni/src/suggest/core/dictionary/digraph_utils.h @@ -21,6 +21,8 @@ namespace latinime { +class BinaryDictionaryHeader; + class DigraphUtils { public: typedef enum { @@ -37,17 +39,14 @@ class DigraphUtils { typedef struct { int first; int second; int compositeGlyph; } digraph_t; - static bool hasDigraphForCodePoint(const int dictFlags, const int compositeGlyphCodePoint); - static int getAllDigraphsForDictionaryAndReturnSize( - const int dictFlags, const digraph_t **const digraphs); - static int getDigraphCodePointForIndex(const int dictFlags, const int compositeGlyphCodePoint, - const DigraphCodePointIndex digraphCodePointIndex); + static bool hasDigraphForCodePoint( + const BinaryDictionaryHeader *const header, const int compositeGlyphCodePoint); static int getDigraphCodePointForIndex(const int compositeGlyphCodePoint, const DigraphCodePointIndex digraphCodePointIndex); private: DISALLOW_IMPLICIT_CONSTRUCTORS(DigraphUtils); - static DigraphType getDigraphTypeForDictionary(const int dictFlags); + static DigraphType getDigraphTypeForDictionary(const BinaryDictionaryHeader *const header); static int getAllDigraphsForDigraphTypeAndReturnSize( const DigraphType digraphType, const digraph_t **const digraphs); static const digraph_t *getDigraphForCodePoint(const int compositeGlyphCodePoint); diff --git a/native/jni/src/suggest/core/dictionary/probability_utils.h b/native/jni/src/suggest/core/dictionary/probability_utils.h index 14d2f8436..f450087d8 100644 --- a/native/jni/src/suggest/core/dictionary/probability_utils.h +++ b/native/jni/src/suggest/core/dictionary/probability_utils.h @@ -17,7 +17,6 @@ #ifndef LATINIME_PROBABILITY_UTILS_H #define LATINIME_PROBABILITY_UTILS_H -#include <map> #include <stdint.h> #include "defines.h" @@ -49,24 +48,6 @@ class ProbabilityUtils { + static_cast<int>(static_cast<float>(bigramProbability + 1) * stepSize); } - // This returns a probability in log space. - static AK_FORCE_INLINE int getProbability(const int position, - const std::map<int, int> *const bigramMap, - const uint8_t *bigramFilter, const int unigramProbability) { - if (!bigramMap || !bigramFilter) { - return backoff(unigramProbability); - } - if (!isInFilter(bigramFilter, position)){ - return backoff(unigramProbability); - } - const std::map<int, int>::const_iterator bigramProbabilityIt = bigramMap->find(position); - if (bigramProbabilityIt != bigramMap->end()) { - const int bigramProbability = bigramProbabilityIt->second; - return computeProbabilityForBigram(unigramProbability, bigramProbability); - } - return backoff(unigramProbability); - } - private: DISALLOW_IMPLICIT_CONSTRUCTORS(ProbabilityUtils); }; diff --git a/native/jni/src/suggest/core/layout/proximity_info.h b/native/jni/src/suggest/core/layout/proximity_info.h index 6ca2fdd7b..534c2c217 100644 --- a/native/jni/src/suggest/core/layout/proximity_info.h +++ b/native/jni/src/suggest/core/layout/proximity_info.h @@ -24,8 +24,6 @@ namespace latinime { -class Correction; - class ProximityInfo { public: ProximityInfo(JNIEnv *env, const jstring localeJStr, @@ -41,7 +39,6 @@ class ProximityInfo { float getNormalizedSquaredDistanceFromCenterFloatG( const int keyId, const int x, const int y, const float verticalScale) const; - bool sameAsTyped(const unsigned short *word, int length) const; int getCodePointOf(const int keyIndex) const; bool hasSweetSpotData(const int keyIndex) const { // When there are no calibration data for a key, @@ -95,8 +92,6 @@ class ProximityInfo { DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo); void initializeG(); - float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const; - bool hasInputCoordinates() const; const int GRID_WIDTH; const int GRID_HEIGHT; diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.cpp b/native/jni/src/suggest/core/layout/proximity_info_state.cpp index 4e53992d4..e8d950060 100644 --- a/native/jni/src/suggest/core/layout/proximity_info_state.cpp +++ b/native/jni/src/suggest/core/layout/proximity_info_state.cpp @@ -156,11 +156,6 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi if (!isGeometric && pointerId == 0) { ProximityInfoStateUtils::initPrimaryInputWord( inputSize, mInputProximities, mPrimaryInputWord); - if (mTouchPositionCorrectionEnabled) { - ProximityInfoStateUtils::initNormalizedSquaredDistances( - mProximityInfo, inputSize, xCoordinates, yCoordinates, mInputProximities, - &mSampledInputXs, &mSampledInputYs, mNormalizedSquaredDistances); - } } if (DEBUG_GEO_FULL) { AKLOGI("ProximityState init finished: %d points out of %d", mSampledInputSize, inputSize); @@ -279,26 +274,6 @@ float ProximityInfoState::getDirection(const int index0, const int index1) const &mSampledInputXs, &mSampledInputYs, index0, index1); } -float ProximityInfoState::getLineToKeyDistance( - const int from, const int to, const int keyId, const bool extend) const { - if (from < 0 || from > mSampledInputSize - 1) { - return 0.0f; - } - if (to < 0 || to > mSampledInputSize - 1) { - return 0.0f; - } - const int x0 = mSampledInputXs[from]; - const int y0 = mSampledInputYs[from]; - const int x1 = mSampledInputXs[to]; - const int y1 = mSampledInputYs[to]; - - const int keyX = mProximityInfo->getKeyCenterXOfKeyIdG(keyId); - const int keyY = mProximityInfo->getKeyCenterYOfKeyIdG(keyId); - - return ProximityInfoUtils::pointToLineSegSquaredDistanceFloat( - keyX, keyY, x0, y0, x1, y1, extend); -} - float ProximityInfoState::getMostProbableString(int *const codePointBuf) const { memcpy(codePointBuf, mMostProbableString, sizeof(mMostProbableString)); return mMostProbableStringProbability; diff --git a/native/jni/src/suggest/core/layout/proximity_info_state.h b/native/jni/src/suggest/core/layout/proximity_info_state.h index 0079ab5b8..cc6410af1 100644 --- a/native/jni/src/suggest/core/layout/proximity_info_state.h +++ b/native/jni/src/suggest/core/layout/proximity_info_state.h @@ -53,7 +53,6 @@ class ProximityInfoState { mSampledSearchKeyVectors(), mTouchPositionCorrectionEnabled(false), mSampledInputSize(0), mMostProbableStringProbability(0.0f) { memset(mInputProximities, 0, sizeof(mInputProximities)); - memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances)); memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord)); memset(mMostProbableString, 0, sizeof(mMostProbableString)); } @@ -91,6 +90,19 @@ class ProximityInfoState { return false; } + // TODO: Promote insertion letter correction if that letter is a proximity of the previous + // letter like follows: + // // Demotion for a word with excessive character + // if (excessiveCount > 0) { + // multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE, &finalFreq); + // if (!lastCharExceeded + // && !proximityInfoState->existsAdjacentProximityChars(excessivePos)) { + // // If an excessive character is not adjacent to the left char or the right char, + // // we will demote this word. + // multiplyRate(WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE, + // &finalFreq); + // } + // } inline bool existsAdjacentProximityChars(const int index) const { if (index < 0 || index >= mSampledInputSize) return false; const int currentCodePoint = getPrimaryCodePointAt(index); @@ -106,12 +118,6 @@ class ProximityInfoState { return false; } - inline int getNormalizedSquaredDistance( - const int inputIndex, const int proximityIndex) const { - return mNormalizedSquaredDistances[ - inputIndex * MAX_PROXIMITY_CHARS_SIZE + proximityIndex]; - } - inline const int *getPrimaryInputWord() const { return mPrimaryInputWord; } @@ -190,24 +196,10 @@ class ProximityInfoState { 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); - ///////////////////////////////////////// - // Defined in proximity_info_state.cpp // - ///////////////////////////////////////// - float calculateNormalizedSquaredDistance(const int keyIndex, const int inputIndex) const; - - float calculateSquaredDistanceFromSweetSpotCenter( - const int keyIndex, const int inputIndex) const; - - ///////////////////////////////////////// - // Defined here // - ///////////////////////////////////////// inline const int *getProximityCodePointsAt(const int index) const { return ProximityInfoStateUtils::getProximityCodePointsAt(mInputProximities, index); @@ -249,7 +241,6 @@ class ProximityInfoState { std::vector<std::vector<int> > mSampledSearchKeyVectors; bool mTouchPositionCorrectionEnabled; int mInputProximities[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH]; - int mNormalizedSquaredDistances[MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH]; int mSampledInputSize; int mPrimaryInputWord[MAX_WORD_LENGTH]; float mMostProbableStringProbability; diff --git a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp index 6f88833a2..1bbae652c 100644 --- a/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp +++ b/native/jni/src/suggest/core/layout/proximity_info_state_utils.cpp @@ -181,48 +181,6 @@ namespace latinime { return squaredDistance / squaredRadius; } -/* static */ void ProximityInfoStateUtils::initNormalizedSquaredDistances( - const ProximityInfo *const proximityInfo, const int inputSize, const int *inputXCoordinates, - const int *inputYCoordinates, const int *const inputProximities, - const std::vector<int> *const sampledInputXs, const std::vector<int> *const sampledInputYs, - int *normalizedSquaredDistances) { - memset(normalizedSquaredDistances, NOT_A_DISTANCE, - sizeof(normalizedSquaredDistances[0]) * MAX_PROXIMITY_CHARS_SIZE * MAX_WORD_LENGTH); - const bool hasInputCoordinates = sampledInputXs->size() > 0 && sampledInputYs->size() > 0; - for (int i = 0; i < inputSize; ++i) { - const int *proximityCodePoints = getProximityCodePointsAt(inputProximities, i); - const int primaryKey = proximityCodePoints[0]; - const int x = inputXCoordinates[i]; - const int y = inputYCoordinates[i]; - if (DEBUG_PROXIMITY_CHARS) { - int a = x + y + primaryKey; - a += 0; - AKLOGI("--- Primary = %c, x = %d, y = %d", primaryKey, x, y); - } - for (int j = 0; j < MAX_PROXIMITY_CHARS_SIZE && proximityCodePoints[j] > 0; ++j) { - const int currentCodePoint = proximityCodePoints[j]; - const float squaredDistance = - hasInputCoordinates ? calculateNormalizedSquaredDistance( - proximityInfo, sampledInputXs, sampledInputYs, - proximityInfo->getKeyIndexOf(currentCodePoint), i) : - ProximityInfoParams::NOT_A_DISTANCE_FLOAT; - if (squaredDistance >= 0.0f) { - normalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] = - static_cast<int>(squaredDistance - * ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR); - } else { - normalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] = - (j == 0) ? MATCH_CHAR_WITHOUT_DISTANCE_INFO : - PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO; - } - if (DEBUG_PROXIMITY_CHARS) { - AKLOGI("--- Proximity (%d) = %c", j, currentCodePoint); - } - } - } - -} - /* static */ void ProximityInfoStateUtils::initGeometricDistanceInfos( const ProximityInfo *const proximityInfo, const int sampledInputSize, const int lastSavedInputSize, const float verticalSweetSpotScale, diff --git a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h index 429dcae0d..9130e87d3 100644 --- a/native/jni/src/suggest/core/layout/touch_position_correction_utils.h +++ b/native/jni/src/suggest/core/layout/touch_position_correction_utils.h @@ -23,31 +23,6 @@ namespace latinime { class TouchPositionCorrectionUtils { public: - // TODO: (OLD) Remove - static float getLengthScalingFactor(const float normalizedSquaredDistance) { - // Promote or demote the score according to the distance from the sweet spot - static const float A = ZERO_DISTANCE_PROMOTION_RATE / 100.0f; - static const float B = 1.0f; - static const float C = 0.5f; - static const float MIN = 0.3f; - static const float R1 = NEUTRAL_SCORE_SQUARED_RADIUS; - static const float R2 = HALF_SCORE_SQUARED_RADIUS; - const float x = normalizedSquaredDistance / static_cast<float>( - ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR); - const float factor = max((x < R1) - ? (A * (R1 - x) + B * x) / R1 - : (B * (R2 - x) + C * (x - R1)) / (R2 - R1), MIN); - // factor is a piecewise linear function like: - // A -_ . - // ^-_ . - // B \ . - // \_ . - // C ------------. - // . - // 0 R1 R2 . - return factor; - } - static float getSweetSpotFactor(const bool isTouchPositionCorrectionEnabled, const float normalizedSquaredDistance) { // Promote or demote the score according to the distance from the sweet spot diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.cpp b/native/jni/src/suggest/core/session/dic_traverse_session.cpp index c398caefa..774d6074e 100644 --- a/native/jni/src/suggest/core/session/dic_traverse_session.cpp +++ b/native/jni/src/suggest/core/session/dic_traverse_session.cpp @@ -19,6 +19,7 @@ #include "defines.h" #include "jni.h" #include "suggest/core/dicnode/dic_node_utils.h" +#include "suggest/core/dictionary/binary_dictionary_header.h" #include "suggest/core/dictionary/binary_dictionary_info.h" #include "suggest/core/dictionary/binary_format.h" #include "suggest/core/dictionary/dictionary.h" @@ -28,9 +29,8 @@ namespace latinime { void DicTraverseSession::init(const Dictionary *const dictionary, const int *prevWord, int prevWordLength, const SuggestOptions *const suggestOptions) { mDictionary = dictionary; - mMultiWordCostMultiplier = BinaryFormat::getMultiWordCostMultiplier( - mDictionary->getBinaryDictionaryInfo()->getDictBuf(), - mDictionary->getDictSize()); + mMultiWordCostMultiplier = mDictionary->getBinaryDictionaryInfo() + ->getHeader()->getMultiWordCostMultiplier(); mSuggestOptions = suggestOptions; if (!prevWord) { mPrevWordPos = NOT_VALID_WORD; @@ -63,10 +63,6 @@ const BinaryDictionaryInfo *DicTraverseSession::getBinaryDictionaryInfo() const return mDictionary->getBinaryDictionaryInfo(); } -int DicTraverseSession::getDictFlags() const { - return mDictionary->getDictFlags(); -} - void DicTraverseSession::resetCache(const int nextActiveCacheSize, const int maxWords) { mDicNodesCache.reset(nextActiveCacheSize, maxWords); mMultiBigramMap.clear(); diff --git a/native/jni/src/suggest/core/session/dic_traverse_session.h b/native/jni/src/suggest/core/session/dic_traverse_session.h index 630b3b59b..f95a0b23d 100644 --- a/native/jni/src/suggest/core/session/dic_traverse_session.h +++ b/native/jni/src/suggest/core/session/dic_traverse_session.h @@ -77,7 +77,6 @@ class DicTraverseSession { // TODO: Remove const BinaryDictionaryInfo *getBinaryDictionaryInfo() const; - int getDictFlags() const; //-------------------- // getters and setters diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp index 1f108e400..6c4a6c166 100644 --- a/native/jni/src/suggest/core/suggest.cpp +++ b/native/jni/src/suggest/core/suggest.cpp @@ -19,6 +19,7 @@ #include "suggest/core/dicnode/dic_node.h" #include "suggest/core/dicnode/dic_node_priority_queue.h" #include "suggest/core/dicnode/dic_node_vector.h" +#include "suggest/core/dictionary/binary_dictionary_info.h" #include "suggest/core/dictionary/dictionary.h" #include "suggest/core/dictionary/digraph_utils.h" #include "suggest/core/dictionary/shortcut_utils.h" @@ -294,7 +295,8 @@ void Suggest::expandCurrentDicNodes(DicTraverseSession *traverseSession) const { processDicNodeAsMatch(traverseSession, childDicNode); continue; } - if (DigraphUtils::hasDigraphForCodePoint(traverseSession->getDictFlags(), + if (DigraphUtils::hasDigraphForCodePoint( + traverseSession->getBinaryDictionaryInfo()->getHeader(), childDicNode->getNodeCodePoint())) { correctionDicNode.initByCopy(childDicNode); correctionDicNode.advanceDigraphIndex(); diff --git a/native/jni/src/suggest/policyimpl/utils/edit_distance.h b/native/jni/src/suggest/policyimpl/utils/edit_distance.h index cbbd66894..0871c37ce 100644 --- a/native/jni/src/suggest/policyimpl/utils/edit_distance.h +++ b/native/jni/src/suggest/policyimpl/utils/edit_distance.h @@ -62,6 +62,26 @@ class EditDistance { return dp[(beforeLength + 1) * (afterLength + 1) - 1]; } + AK_FORCE_INLINE static void dumpEditDistance10ForDebug(const float *const editDistanceTable, + const int editDistanceTableWidth, const int outputLength) { + if (DEBUG_DICT) { + AKLOGI("EditDistanceTable"); + for (int i = 0; i <= 10; ++i) { + float c[11]; + for (int j = 0; j <= 10; ++j) { + if (j < editDistanceTableWidth + 1 && i < outputLength + 1) { + c[j] = (editDistanceTable + i * (editDistanceTableWidth + 1))[j]; + } else { + c[j] = -1.0f; + } + } + AKLOGI("[ %f, %f, %f, %f, %f, %f, %f, %f, %f, %f, %f ]", + c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7], c[8], c[9], c[10]); + (void)c; // To suppress compiler warning + } + } + } + private: DISALLOW_IMPLICIT_CONSTRUCTORS(EditDistance); }; diff --git a/native/jni/src/utils/autocorrection_threshold_utils.cpp b/native/jni/src/utils/autocorrection_threshold_utils.cpp new file mode 100644 index 000000000..3406e0f8e --- /dev/null +++ b/native/jni/src/utils/autocorrection_threshold_utils.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2013 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 "utils/autocorrection_threshold_utils.h" + +#include <cmath> + +#include "defines.h" +#include "suggest/policyimpl/utils/edit_distance.h" +#include "suggest/policyimpl/utils/damerau_levenshtein_edit_distance_policy.h" + +namespace latinime { + +const int AutocorrectionThresholdUtils::MAX_INITIAL_SCORE = 255; +const int AutocorrectionThresholdUtils::TYPED_LETTER_MULTIPLIER = 2; +const int AutocorrectionThresholdUtils::FULL_WORD_MULTIPLIER = 2; + +/* static */ int AutocorrectionThresholdUtils::editDistance(const int *before, + const int beforeLength, const int *after, const int afterLength) { + const DamerauLevenshteinEditDistancePolicy daemaruLevenshtein( + before, beforeLength, after, afterLength); + return static_cast<int>(EditDistance::getEditDistance(&daemaruLevenshtein)); +} + +// In dictionary.cpp, getSuggestion() method, +// When USE_SUGGEST_INTERFACE_FOR_TYPING is true: +// +// // TODO: Revise the following logic thoroughly by referring to the logic +// // marked as "Otherwise" below. +// SUGGEST_INTERFACE_OUTPUT_SCALE was multiplied to the original suggestion scores to convert +// them to integers. +// score = (int)((original score) * SUGGEST_INTERFACE_OUTPUT_SCALE) +// Undo the scaling here to recover the original score. +// normalizedScore = ((float)score) / SUGGEST_INTERFACE_OUTPUT_SCALE +// +// Otherwise: suggestion scores are computed using the below formula. +// original score +// := powf(mTypedLetterMultiplier (this is defined 2), +// (the number of matched characters between typed word and suggested word)) +// * (individual word's score which defined in the unigram dictionary, +// and this score is defined in range [0, 255].) +// Then, the following processing is applied. +// - If the dictionary word is matched up to the point of the user entry +// (full match up to min(before.length(), after.length()) +// => Then multiply by FULL_MATCHED_WORDS_PROMOTION_RATE (this is defined 1.2) +// - If the word is a true full match except for differences in accents or +// capitalization, then treat it as if the score was 255. +// - If before.length() == after.length() +// => multiply by mFullWordMultiplier (this is defined 2)) +// So, maximum original score is powf(2, min(before.length(), after.length())) * 255 * 2 * 1.2 +// For historical reasons we ignore the 1.2 modifier (because the measure for a good +// autocorrection threshold was done at a time when it didn't exist). This doesn't change +// the result. +// So, we can normalize original score by dividing powf(2, min(b.l(),a.l())) * 255 * 2. + +/* static */ float AutocorrectionThresholdUtils::calcNormalizedScore(const int *before, + const int beforeLength, const int *after, const int afterLength, const int score) { + if (0 == beforeLength || 0 == afterLength) { + return 0.0f; + } + const int distance = editDistance(before, beforeLength, after, afterLength); + int spaceCount = 0; + for (int i = 0; i < afterLength; ++i) { + if (after[i] == KEYCODE_SPACE) { + ++spaceCount; + } + } + + if (spaceCount == afterLength) { + return 0.0f; + } + + // add a weight based on edit distance. + // distance <= max(afterLength, beforeLength) == afterLength, + // so, 0 <= distance / afterLength <= 1 + const float weight = 1.0f - static_cast<float>(distance) / static_cast<float>(afterLength); + + // TODO: Revise the following logic thoroughly by referring to... + if (true /* USE_SUGGEST_INTERFACE_FOR_TYPING */) { + return (static_cast<float>(score) / SUGGEST_INTERFACE_OUTPUT_SCALE) * weight; + } + // ...this logic. + const float maxScore = score >= S_INT_MAX ? static_cast<float>(S_INT_MAX) + : static_cast<float>(MAX_INITIAL_SCORE) + * powf(static_cast<float>(TYPED_LETTER_MULTIPLIER), + static_cast<float>(min(beforeLength, afterLength - spaceCount))) + * static_cast<float>(FULL_WORD_MULTIPLIER); + + return (static_cast<float>(score) / maxScore) * weight; +} + +} // namespace latinime diff --git a/native/jni/src/utils/autocorrection_threshold_utils.h b/native/jni/src/utils/autocorrection_threshold_utils.h new file mode 100644 index 000000000..c7537a6a5 --- /dev/null +++ b/native/jni/src/utils/autocorrection_threshold_utils.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LATINIME_AUTOCORRECTION_THRESHOLD_UTILS_H +#define LATINIME_AUTOCORRECTION_THRESHOLD_UTILS_H + +#include "defines.h" + +namespace latinime { + +class AutocorrectionThresholdUtils { + public: + 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: + DISALLOW_IMPLICIT_CONSTRUCTORS(AutocorrectionThresholdUtils); + + static const int MAX_INITIAL_SCORE; + static const int TYPED_LETTER_MULTIPLIER; + static const int FULL_WORD_MULTIPLIER; +}; +} // namespace latinime +#endif // LATINIME_AUTOCORRECTION_THRESHOLD_UTILS_H diff --git a/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java b/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java new file mode 100644 index 000000000..b311f5d37 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/Base64ReaderTests.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2013 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.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import java.io.EOFException; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.StringReader; + +@SmallTest +public class Base64ReaderTests extends AndroidTestCase { + private static final String EMPTY_STRING = ""; + private static final String INCOMPLETE_CHAR1 = "Q"; + // Encode 'A'. + private static final String INCOMPLETE_CHAR2 = "QQ"; + // Encode 'A', 'B' + private static final String INCOMPLETE_CHAR3 = "QUI"; + // Encode 'A', 'B', 'C' + private static final String COMPLETE_CHAR4 = "QUJD"; + private static final String ALL_BYTE_PATTERN = + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIj\n" + + "JCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH\n" + + "SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWpr\n" + + "bG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n" + + "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKz\n" + + "tLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX\n" + + "2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7\n" + + "/P3+/w=="; + + public void test0CharInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(EMPTY_STRING))); + try { + reader.readUint8(); + fail("0 char"); + } catch (final EOFException e) { + assertEquals("0 char", 0, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test1CharInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR1))); + try { + reader.readUint8(); + fail("1 char"); + } catch (final EOFException e) { + assertEquals("1 char", 0, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test2CharsInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR2))); + try { + final int v1 = reader.readUint8(); + assertEquals("2 chars pos 0", 'A', v1); + reader.readUint8(); + fail("2 chars"); + } catch (final EOFException e) { + assertEquals("2 chars", 1, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test3CharsInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR3))); + try { + final int v1 = reader.readUint8(); + assertEquals("3 chars pos 0", 'A', v1); + final int v2 = reader.readUint8(); + assertEquals("3 chars pos 1", 'B', v2); + reader.readUint8(); + fail("3 chars"); + } catch (final EOFException e) { + assertEquals("3 chars", 2, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test4CharsInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(COMPLETE_CHAR4))); + try { + final int v1 = reader.readUint8(); + assertEquals("4 chars pos 0", 'A', v1); + final int v2 = reader.readUint8(); + assertEquals("4 chars pos 1", 'B', v2); + final int v3 = reader.readUint8(); + assertEquals("4 chars pos 2", 'C', v3); + reader.readUint8(); + fail("4 chars"); + } catch (final EOFException e) { + assertEquals("4 chars", 3, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void testAllBytePatternInt8() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(ALL_BYTE_PATTERN))); + try { + for (int i = 0; i <= 0xff; i++) { + final int v = reader.readUint8(); + assertEquals("value: all byte pattern: pos " + i, i, v); + assertEquals("count: all byte pattern: pos " + i, i + 1, reader.getByteCount()); + } + } catch (final EOFException e) { + assertEquals("all byte pattern", 256, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test0CharInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(EMPTY_STRING))); + try { + reader.readInt16(); + fail("0 char"); + } catch (final EOFException e) { + assertEquals("0 char", 0, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test1CharInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR1))); + try { + reader.readInt16(); + fail("1 char"); + } catch (final EOFException e) { + assertEquals("1 char", 0, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test2CharsInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR2))); + try { + reader.readInt16(); + fail("2 chars"); + } catch (final EOFException e) { + assertEquals("2 chars", 1, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test3CharsInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(INCOMPLETE_CHAR3))); + try { + final short v1 = reader.readInt16(); + assertEquals("3 chars pos 0", 'A' << 8 | 'B', v1); + reader.readInt16(); + fail("3 chars"); + } catch (final EOFException e) { + assertEquals("3 chars", 2, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void test4CharsInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(COMPLETE_CHAR4))); + try { + final short v1 = reader.readInt16(); + assertEquals("4 chars pos 0", 'A' << 8 | 'B', v1); + reader.readInt16(); + fail("4 chars"); + } catch (final EOFException e) { + assertEquals("4 chars", 3, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } + + public void testAllBytePatternInt16() { + final Base64Reader reader = new Base64Reader( + new LineNumberReader(new StringReader(ALL_BYTE_PATTERN))); + try { + for (int i = 0; i <= 0xff; i += 2) { + final short v = reader.readInt16(); + final short expected = (short)(i << 8 | (i + 1)); + assertEquals("value: all byte pattern: pos " + i, expected, v); + assertEquals("count: all byte pattern: pos " + i, i + 2, reader.getByteCount()); + } + } catch (final EOFException e) { + assertEquals("all byte pattern", 256, reader.getByteCount()); + } catch (final IOException e) { + fail("IOException: " + e); + } + } +} |