Merge "Allow downloadable dictionaries in development builds"
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index fb973f3..42f343a 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 77a76b5..f1212f9 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 cbb07dd..0bdb06e 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 0000000..7630cde
--- /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">"თქვენს მიერ მობილურ მოწყობილობაზე არჩეული ენისათვის ხელმისაწვდომია ლექსიკონი.&lt;br/&gt; გირჩევთ, &lt;b&gt;ჩამოტვირთოთ&lt;/b&gt; <xliff:g id="LANGUAGE">%1$s</xliff:g> ლექსიკონი, რათა გაიმარტივოთ ტექსტის შეყვანა.&lt;br/&gt; &lt;br/&gt; ჩამოტვირთვას შესაძლოა დაჭირდეს ერთი ან ორი წუთი 3G სისწრაფეზე. თუ ულიმიტო არ გაგაჩნიათ &lt;b&gt; მობილური ინტერნეტის ტარიფი&lt;/b&gt;.&lt;br/&amp;gt, შესაძლოა ჩამოტვირთვა დამატებით გადასახადებთან იყოს დაკავშირებული; თუ არ ხართ დარწმუნებული მობილური ინტერნეტის აქტიური ტარიფის შესახებ, გირჩევთ იპოვოთ Wi-Fi კავშირი და ავტომატურად დაიწყოთ ჩამოტვირთვა.&lt;br/&gt; &lt;br/&gt; რჩევა: ლექსიკონების ჩამოტვირთვა და ამოშლა შესაძლებელია სექციიდან &lt;b&gt;ენა და შეყვანა&lt;/b&gt; სექციიდან, თქვენი მობილური მოწყობილობის &lt;b&gt;პარამეტრების&lt;/b&gt; მენიუში."</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 53e46aa..116ce16 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 0474b1c..e8d9225 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 6961588..df0e3f0 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -28,13 +28,13 @@
      * 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 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 46bb554..6e3dd71 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -54,12 +54,7 @@
     /**
      * 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 @@
             // 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 @@
         // 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 1511dbc..dac1213 100644
--- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
+++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java
@@ -36,8 +36,6 @@
  * 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 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 d4051f7..a074b63 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -80,6 +80,14 @@
     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 @@
         boolean mNoSettingsKey;
         boolean mLanguageSwitchKeyEnabled;
         InputMethodSubtype mSubtype;
+        boolean mIsSpellChecker;
         int mOrientation;
         int mKeyboardWidth;
         int mKeyboardHeight;
@@ -185,7 +194,18 @@
                     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 @@
             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 @@
         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 @@
             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 0f3cd78..fb69e22 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 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 2eefd6a..211ef5f 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 @@
      * @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 @@
         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 df7bad8..9d47849 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
@@ -30,6 +30,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Locale;
 
 /**
@@ -301,12 +302,14 @@
 
     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 0dd302a..94513e6 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -54,13 +54,6 @@
         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 9fefb58..a6149c6 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.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.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 @@
 
     @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 20ce6d4..1fad765 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.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.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 @@
                 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 @@
                 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 @@
 
     @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 282b579..1eca68a 100644
--- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
+++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java
@@ -43,20 +43,23 @@
     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 @@
         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 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 604ebee..63d2fec 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.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 @@
         }
 
         // 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 8a2de88..a68f98f 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.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 @@
         }
     }
 
+    /*
+     * 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 @@
 
     /**
      * 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 78a6478..13fa9d9 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.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.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 @@
 
     private static final boolean ENABLE_WELCOME_VIDEO = true;
 
+    private InputMethodManager mImm;
+
     private View mSetupWizard;
     private View mWelcomeScreen;
     private View mSetupScreen;
@@ -69,15 +71,19 @@
     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 @@
             }
             switch (msg.what) {
             case MSG_POLLING_IME_SETTINGS:
-                if (SetupActivity.isThisImeEnabled(setupWizardActivity)) {
+                if (SetupActivity.isThisImeEnabled(setupWizardActivity, mImmInHandler)) {
                     setupWizardActivity.invokeSetupWizardOfThisIme();
                     return;
                 }
@@ -112,11 +118,12 @@
         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 @@
                 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 @@
 
     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 @@
 
     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 0000000..3eca6e7
--- /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 aa4a866..e890b74 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -429,6 +429,7 @@
         mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS);
 
         resetLogBuffers();
+        cancelFeedbackDialog();
     }
 
     public void abort() {
@@ -465,6 +466,12 @@
         presentFeedbackDialog(latinIME);
     }
 
+    public void presentFeedbackDialogFromSettings() {
+        if (mLatinIME != null) {
+            presentFeedbackDialog(mLatinIME);
+        }
+    }
+
     public void presentFeedbackDialog(final LatinIME latinIME) {
         if (isMakingUserRecording()) {
             saveRecording();
@@ -701,13 +708,19 @@
         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 7ca4057..1cdfbe4 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -47,15 +47,15 @@
 
 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 @@
         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 f607937..1225e7f 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 @@
     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 @@
     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 e0edff5..a3cf6a4 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -268,9 +268,6 @@
 #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 @@
 #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 6b80ed8..0000000
--- 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 47dcef2..0000000
--- 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 a63d4aa..0000000
--- 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 59d1b19..53e2df6 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 @@
     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 8b7a253..06d0e9d 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 @@
 
     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
similarity index 90%
rename from native/jni/src/suggest/core/dictionary/binary_dictionary_format.cpp
rename to native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
index 50e0211..737df63 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 @@
 // 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 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
similarity index 67%
rename from native/jni/src/suggest/core/dictionary/binary_dictionary_format.h
rename to native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
index 3aa1662..c0fd561 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 @@
 
     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 0000000..04bb81f
--- /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 0000000..9db0003
--- /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 0000000..c09a78f
--- /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 0000000..6e9dca7
--- /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 8508c67..0b77e5e 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 @@
     }
 
     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 c82065f..0a290d8 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 @@
     // 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 @@
             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 @@
     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 @@
     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 @@
     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 832b747..d3321f6 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 @@
      * 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 @@
 
     // 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 @@
         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 2d4ad5d..561e22d 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::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 @@
     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 1f25080..151f261 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -66,22 +66,20 @@
     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 f53e56e..af378b1 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 @@
         { 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 @@
 
 // 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::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 c120594..9d74fe3 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 @@
 
     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 14d2f84..f450087 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 @@
                 + 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 6ca2fdd..534c2c2 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 @@
     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 @@
     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 4e53992..e8d9500 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 @@
     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 @@
             &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 0079ab5..cc6410a 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 @@
               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 @@
         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 @@
         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 @@
 
     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 @@
     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 6f88833..1bbae65 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 @@
     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 429dcae..9130e87 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 c398cae..774d607 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 @@
 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 @@
     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 630b3b5..f95a0b2 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 @@
 
     // 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 1f108e4..6c4a6c1 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 @@
                     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 cbbd668..0871c37 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 @@
         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 0000000..3406e0f
--- /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 0000000..c7537a6
--- /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 0000000..b311f5d
--- /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);
+        }
+    }
+}