Merge "Quit creating empty dictionary file in ExpandableBinaryDictionary."
diff --git a/java/res/values-da/strings-talkback-descriptions.xml b/java/res/values-da/strings-talkback-descriptions.xml
index 658f476..2d613d6 100644
--- a/java/res/values-da/strings-talkback-descriptions.xml
+++ b/java/res/values-da/strings-talkback-descriptions.xml
@@ -26,7 +26,7 @@
     <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g> retter <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> til <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> udfører automatisk stavekontrol"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"Tastekode %d"</string>
-    <string name="spoken_description_shift" msgid="7209798151676638728">"Shift-tast"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"Shift er aktiveret (tryk for at deaktivere)"</string>
     <string name="spoken_description_caps_lock" msgid="5020582161133170892">"Caps lock er slået til (tryk for at deaktivere)"</string>
     <string name="spoken_description_delete" msgid="3878902286264983302">"Slet"</string>
@@ -36,9 +36,9 @@
     <string name="spoken_description_settings" msgid="7281251004003143204">"Indstillinger"</string>
     <string name="spoken_description_tab" msgid="8210782459446866716">"Fane"</string>
     <string name="spoken_description_space" msgid="5908716896642059145">"Mellemrum"</string>
-    <string name="spoken_description_mic" msgid="6153138783813452464">"Taleinput"</string>
+    <string name="spoken_description_mic" msgid="6153138783813452464">"Stemmeinput"</string>
     <string name="spoken_description_emoji" msgid="7990051553008088470">"Emoji"</string>
-    <string name="spoken_description_return" msgid="3183692287397645708">"Tilbage"</string>
+    <string name="spoken_description_return" msgid="3183692287397645708">"Return"</string>
     <string name="spoken_description_search" msgid="5099937658231911288">"Søgning"</string>
     <string name="spoken_description_dot" msgid="5644176501632325560">"Prik"</string>
     <string name="spoken_description_language_switch" msgid="6818666779313544553">"Skift sprog"</string>
diff --git a/java/res/values-fi/strings-talkback-descriptions.xml b/java/res/values-fi/strings-talkback-descriptions.xml
new file mode 100644
index 0000000..2d0d7a7
--- /dev/null
+++ b/java/res/values-fi/strings-talkback-descriptions.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2014, 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="spoken_use_headphones" msgid="4313642710742229868">"Liitä kuulokkeet, niin kuulet mitä näppäimiä painat kirjoittaessasi salasanaa."</string>
+    <string name="spoken_current_text_is" msgid="4240549866156675799">"Nykyinen teksti on %s"</string>
+    <string name="spoken_no_text_entered" msgid="1711276837961785646">"Ei kirjoitettua tekstiä"</string>
+    <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g> korjaa sanan <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> sanaksi <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> suorittaa automaattisen korjauksen"</string>
+    <string name="spoken_description_unknown" msgid="2382510329910793539">"Näppäimen koodi %d"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"Vaihto"</string>
+    <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"Vaihto päällä (poista käytöstä napauttamalla)"</string>
+    <string name="spoken_description_caps_lock" msgid="5020582161133170892">"Caps Lock päällä (poista käytöstä napauttamalla)"</string>
+    <string name="spoken_description_delete" msgid="3878902286264983302">"Delete"</string>
+    <string name="spoken_description_to_symbol" msgid="8244903740201126590">"Symbolit"</string>
+    <string name="spoken_description_to_alpha" msgid="4081215210530031950">"Kirjaimet"</string>
+    <string name="spoken_description_to_numeric" msgid="4560261331530795682">"Numerot"</string>
+    <string name="spoken_description_settings" msgid="7281251004003143204">"Asetukset"</string>
+    <string name="spoken_description_tab" msgid="8210782459446866716">"Sarkain"</string>
+    <string name="spoken_description_space" msgid="5908716896642059145">"Välilyönti"</string>
+    <string name="spoken_description_mic" msgid="6153138783813452464">"Äänisyöte"</string>
+    <string name="spoken_description_emoji" msgid="7990051553008088470">"Emoji"</string>
+    <string name="spoken_description_return" msgid="3183692287397645708">"Enter"</string>
+    <string name="spoken_description_search" msgid="5099937658231911288">"Haku"</string>
+    <string name="spoken_description_dot" msgid="5644176501632325560">"Piste"</string>
+    <string name="spoken_description_language_switch" msgid="6818666779313544553">"Vaihda kieli"</string>
+    <string name="spoken_description_action_next" msgid="431761808119616962">"Seuraava"</string>
+    <string name="spoken_description_action_previous" msgid="2919072174697865110">"Edellinen"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5107180516341258979">"Vaihto päällä"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"Caps Lock päällä"</string>
+    <string name="spoken_description_shiftmode_off" msgid="5039126122829961331">"Vaihto pois päältä"</string>
+    <string name="spoken_description_mode_symbol" msgid="111186851131446691">"Symbolit-tila"</string>
+    <string name="spoken_description_mode_alpha" msgid="4676004119618778911">"Näppäimistötila"</string>
+    <string name="spoken_description_mode_phone" msgid="2061220553756692903">"Puhelintila"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="7879963803547701090">"Puhelinsymbolit-tila"</string>
+    <string name="announce_keyboard_hidden" msgid="2313574218950517779">"Näppäimistö on piilotettu"</string>
+    <string name="announce_keyboard_mode" msgid="6698257917367823205">"Näytetään näppäimistö <xliff:g id="KEYBOARD_MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="6597407244976713364">"päivämäärä"</string>
+    <string name="keyboard_mode_date_time" msgid="3642804408726668808">"päivämäärä ja aika"</string>
+    <string name="keyboard_mode_email" msgid="1239682082047693644">"sähköposti"</string>
+    <string name="keyboard_mode_im" msgid="3812086215529493501">"viestit"</string>
+    <string name="keyboard_mode_number" msgid="5395042245837996809">"numero"</string>
+    <string name="keyboard_mode_phone" msgid="2486230278064523665">"puhelin"</string>
+    <string name="keyboard_mode_text" msgid="9138789594969187494">"teksti"</string>
+    <string name="keyboard_mode_time" msgid="8558297845514402675">"aika"</string>
+    <string name="keyboard_mode_url" msgid="8072011652949962550">"URL-osoite"</string>
+    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Viimeisimmät"</string>
+    <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"Ihmiset"</string>
+    <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"Esineet"</string>
+    <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"Luonto"</string>
+    <string name="spoken_descrption_emoji_category_places" msgid="1163315840948545317">"Paikat"</string>
+    <string name="spoken_descrption_emoji_category_symbols" msgid="474680659024880601">"Symbolit"</string>
+    <string name="spoken_descrption_emoji_category_emoticons" msgid="456737544787823539">"Hymiöt"</string>
+</resources>
diff --git a/java/res/values-ms-rMY/strings-talkback-descriptions.xml b/java/res/values-ms-rMY/strings-talkback-descriptions.xml
new file mode 100644
index 0000000..29c5fd8
--- /dev/null
+++ b/java/res/values-ms-rMY/strings-talkback-descriptions.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 2014, 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="spoken_use_headphones" msgid="4313642710742229868">"Pasangkan set kepala untuk mendengar kekunci kata laluan disebut dengan kuat."</string>
+    <string name="spoken_current_text_is" msgid="4240549866156675799">"Teks semasa adalah %s"</string>
+    <string name="spoken_no_text_entered" msgid="1711276837961785646">"Tiada teks dimasukkan"</string>
+    <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g> membetulkan <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> menjadi <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
+    <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> melakukan auto pembetulan"</string>
+    <string name="spoken_description_unknown" msgid="2382510329910793539">"Kod kunci %d"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
+    <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"Kunci anjak dihidupkan (ketik untuk melumpuhkan)"</string>
+    <string name="spoken_description_caps_lock" msgid="5020582161133170892">"Kunci huruf besar dihidupkan (ketik untuk melumpuhkan)"</string>
+    <string name="spoken_description_delete" msgid="3878902286264983302">"Padam"</string>
+    <string name="spoken_description_to_symbol" msgid="8244903740201126590">"Simbol"</string>
+    <string name="spoken_description_to_alpha" msgid="4081215210530031950">"Huruf"</string>
+    <string name="spoken_description_to_numeric" msgid="4560261331530795682">"Nombor"</string>
+    <string name="spoken_description_settings" msgid="7281251004003143204">"Tetapan"</string>
+    <string name="spoken_description_tab" msgid="8210782459446866716">"Tab"</string>
+    <string name="spoken_description_space" msgid="5908716896642059145">"{0}&lt;td class=\"shortcuts\"&gt;{/0} Space"</string>
+    <string name="spoken_description_mic" msgid="6153138783813452464">"Input suara"</string>
+    <string name="spoken_description_emoji" msgid="7990051553008088470">"Emoji"</string>
+    <string name="spoken_description_return" msgid="3183692287397645708">"Kembali"</string>
+    <string name="spoken_description_search" msgid="5099937658231911288">"Carian"</string>
+    <string name="spoken_description_dot" msgid="5644176501632325560">"Titik"</string>
+    <string name="spoken_description_language_switch" msgid="6818666779313544553">"Tukar bahasa"</string>
+    <string name="spoken_description_action_next" msgid="431761808119616962">"Slps"</string>
+    <string name="spoken_description_action_previous" msgid="2919072174697865110">"Sebelumnya"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5107180516341258979">"Kunci anjak didayakan"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"Kunci huruf besar didayakan"</string>
+    <string name="spoken_description_shiftmode_off" msgid="5039126122829961331">"Kunci anjak dilumpuhkan"</string>
+    <string name="spoken_description_mode_symbol" msgid="111186851131446691">"Mod simbol"</string>
+    <string name="spoken_description_mode_alpha" msgid="4676004119618778911">"Mod huruf"</string>
+    <string name="spoken_description_mode_phone" msgid="2061220553756692903">"Mod telefon"</string>
+    <string name="spoken_description_mode_phone_shift" msgid="7879963803547701090">"Mod simbol telefon"</string>
+    <string name="announce_keyboard_hidden" msgid="2313574218950517779">"Papan kekunci tersembunyi"</string>
+    <string name="announce_keyboard_mode" msgid="6698257917367823205">"Menunjukkan papan kekunci <xliff:g id="KEYBOARD_MODE">%s</xliff:g>"</string>
+    <string name="keyboard_mode_date" msgid="6597407244976713364">"tarikh"</string>
+    <string name="keyboard_mode_date_time" msgid="3642804408726668808">"tarikh dan masa"</string>
+    <string name="keyboard_mode_email" msgid="1239682082047693644">"e-mel"</string>
+    <string name="keyboard_mode_im" msgid="3812086215529493501">"pemesejan"</string>
+    <string name="keyboard_mode_number" msgid="5395042245837996809">"nombor"</string>
+    <string name="keyboard_mode_phone" msgid="2486230278064523665">"telefon"</string>
+    <string name="keyboard_mode_text" msgid="9138789594969187494">"teks"</string>
+    <string name="keyboard_mode_time" msgid="8558297845514402675">"masa"</string>
+    <string name="keyboard_mode_url" msgid="8072011652949962550">"URL"</string>
+    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Terkini"</string>
+    <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"Orang"</string>
+    <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"Objek"</string>
+    <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"Alam Semula Jadi"</string>
+    <string name="spoken_descrption_emoji_category_places" msgid="1163315840948545317">"Tempat"</string>
+    <string name="spoken_descrption_emoji_category_symbols" msgid="474680659024880601">"Simbol"</string>
+    <string name="spoken_descrption_emoji_category_emoticons" msgid="456737544787823539">"Emotikon"</string>
+</resources>
diff --git a/java/res/values-nb/strings-talkback-descriptions.xml b/java/res/values-nb/strings-talkback-descriptions.xml
index 5a29d80..96edf38 100644
--- a/java/res/values-nb/strings-talkback-descriptions.xml
+++ b/java/res/values-nb/strings-talkback-descriptions.xml
@@ -60,7 +60,7 @@
     <string name="keyboard_mode_number" msgid="5395042245837996809">"tall"</string>
     <string name="keyboard_mode_phone" msgid="2486230278064523665">"telefon"</string>
     <string name="keyboard_mode_text" msgid="9138789594969187494">"tekst"</string>
-    <string name="keyboard_mode_time" msgid="8558297845514402675">"tid"</string>
+    <string name="keyboard_mode_time" msgid="8558297845514402675">"klokkeslett"</string>
     <string name="keyboard_mode_url" msgid="8072011652949962550">"nettadresse"</string>
     <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Nylige"</string>
     <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"Personer"</string>
diff --git a/java/res/values-sw/strings-talkback-descriptions.xml b/java/res/values-sw/strings-talkback-descriptions.xml
index 120489f..e9ca282 100644
--- a/java/res/values-sw/strings-talkback-descriptions.xml
+++ b/java/res/values-sw/strings-talkback-descriptions.xml
@@ -20,13 +20,13 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="spoken_use_headphones" msgid="4313642710742229868">"Chomeka kifaa cha sauti ili usikie vitufe vya nenosiri vinayozungumwa kwa sauti."</string>
+    <string name="spoken_use_headphones" msgid="4313642710742229868">"Chomeka kifaa cha sauti ili usikie vitufe vya nenosiri vikitamkwa kwa sauti."</string>
     <string name="spoken_current_text_is" msgid="4240549866156675799">"Maandishi ya sasa ni %s"</string>
     <string name="spoken_no_text_entered" msgid="1711276837961785646">"Hakuna maandishi yaliyoingizwa"</string>
     <string name="spoken_auto_correct" msgid="8989324692167993804">"<xliff:g id="KEY_NAME">%1$s</xliff:g> hurekebisha <xliff:g id="ORIGINAL_WORD">%2$s</xliff:g> kuwa <xliff:g id="CORRECTED_WORD">%3$s</xliff:g>"</string>
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"<xliff:g id="KEY_NAME">%1$s</xliff:g> hufanya marekebisho otomatiki"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"Msimbo wa kitufe %d"</string>
-    <string name="spoken_description_shift" msgid="7209798151676638728">"Badilisha"</string>
+    <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
     <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"Shift imewashwa (gonga ili kuizima)"</string>
     <string name="spoken_description_caps_lock" msgid="5020582161133170892">"Caps lock imewashwa (gonga ili kuizima)"</string>
     <string name="spoken_description_delete" msgid="3878902286264983302">"Futa"</string>
@@ -40,15 +40,15 @@
     <string name="spoken_description_emoji" msgid="7990051553008088470">"Emoji"</string>
     <string name="spoken_description_return" msgid="3183692287397645708">"Rudi"</string>
     <string name="spoken_description_search" msgid="5099937658231911288">"Utafutaji"</string>
-    <string name="spoken_description_dot" msgid="5644176501632325560">"Doa"</string>
-    <string name="spoken_description_language_switch" msgid="6818666779313544553">"Badili lugha"</string>
+    <string name="spoken_description_dot" msgid="5644176501632325560">"Nukta"</string>
+    <string name="spoken_description_language_switch" msgid="6818666779313544553">"Badilisha lugha"</string>
     <string name="spoken_description_action_next" msgid="431761808119616962">"Linalofuata"</string>
-    <string name="spoken_description_action_previous" msgid="2919072174697865110">"Iliyotangulia"</string>
+    <string name="spoken_description_action_previous" msgid="2919072174697865110">"Iililotangulia"</string>
     <string name="spoken_description_shiftmode_on" msgid="5107180516341258979">"Shift imewashwa"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"Herufi kubwa zimewashwa"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"Caps lock imewashwa"</string>
     <string name="spoken_description_shiftmode_off" msgid="5039126122829961331">"Shift imezimwa"</string>
     <string name="spoken_description_mode_symbol" msgid="111186851131446691">"Hali ya alama"</string>
-    <string name="spoken_description_mode_alpha" msgid="4676004119618778911">"Hali ya barua"</string>
+    <string name="spoken_description_mode_alpha" msgid="4676004119618778911">"Hali ya herufi"</string>
     <string name="spoken_description_mode_phone" msgid="2061220553756692903">"Hali ya simu"</string>
     <string name="spoken_description_mode_phone_shift" msgid="7879963803547701090">"Hali ya alama za simu"</string>
     <string name="announce_keyboard_hidden" msgid="2313574218950517779">"Kibodi imefichwa"</string>
@@ -62,7 +62,7 @@
     <string name="keyboard_mode_text" msgid="9138789594969187494">"maandishi"</string>
     <string name="keyboard_mode_time" msgid="8558297845514402675">"wakati"</string>
     <string name="keyboard_mode_url" msgid="8072011652949962550">"URL"</string>
-    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Zilizotumika karibuni"</string>
+    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"Zilizotumika majuzi"</string>
     <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"Watu"</string>
     <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"Vitu"</string>
     <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"Maumbile"</string>
diff --git a/java/res/values-th/strings-talkback-descriptions.xml b/java/res/values-th/strings-talkback-descriptions.xml
index 4e46987..eb712ae 100644
--- a/java/res/values-th/strings-talkback-descriptions.xml
+++ b/java/res/values-th/strings-talkback-descriptions.xml
@@ -35,7 +35,7 @@
     <string name="spoken_description_to_numeric" msgid="4560261331530795682">"หมายเลข"</string>
     <string name="spoken_description_settings" msgid="7281251004003143204">"การตั้งค่า"</string>
     <string name="spoken_description_tab" msgid="8210782459446866716">"แท็บ"</string>
-    <string name="spoken_description_space" msgid="5908716896642059145">"{0}&lt;td{1} {/1}class=\"shortcuts\"&gt;{/0} Space"</string>
+    <string name="spoken_description_space" msgid="5908716896642059145">"วรรค"</string>
     <string name="spoken_description_mic" msgid="6153138783813452464">"การป้อนข้อมูลด้วยเสียง"</string>
     <string name="spoken_description_emoji" msgid="7990051553008088470">"อีโมจิ"</string>
     <string name="spoken_description_return" msgid="3183692287397645708">"ส่งคืน"</string>
diff --git a/java/res/values-zh-rCN/strings-talkback-descriptions.xml b/java/res/values-zh-rCN/strings-talkback-descriptions.xml
index 73594b2..93f89e0 100644
--- a/java/res/values-zh-rCN/strings-talkback-descriptions.xml
+++ b/java/res/values-zh-rCN/strings-talkback-descriptions.xml
@@ -27,8 +27,8 @@
     <string name="spoken_auto_correct_obscured" msgid="7769449372355268412">"按<xliff:g id="KEY_NAME">%1$s</xliff:g>键可进行自动更正"</string>
     <string name="spoken_description_unknown" msgid="2382510329910793539">"键码为%d"</string>
     <string name="spoken_description_shift" msgid="7209798151676638728">"Shift"</string>
-    <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"Shift模式已启用(点按即可停用)"</string>
-    <string name="spoken_description_caps_lock" msgid="5020582161133170892">"大写锁定模式已启用(点按即可停用)"</string>
+    <string name="spoken_description_shift_shifted" msgid="1609924271343916689">"已开启Shift模式(点按即可关闭)"</string>
+    <string name="spoken_description_caps_lock" msgid="5020582161133170892">"已锁定大写模式(点按即可关闭)"</string>
     <string name="spoken_description_delete" msgid="3878902286264983302">"删除"</string>
     <string name="spoken_description_to_symbol" msgid="8244903740201126590">"符号"</string>
     <string name="spoken_description_to_alpha" msgid="4081215210530031950">"字母"</string>
@@ -38,15 +38,15 @@
     <string name="spoken_description_space" msgid="5908716896642059145">"空格"</string>
     <string name="spoken_description_mic" msgid="6153138783813452464">"语音输入"</string>
     <string name="spoken_description_emoji" msgid="7990051553008088470">"表情符号"</string>
-    <string name="spoken_description_return" msgid="3183692287397645708">"返回"</string>
+    <string name="spoken_description_return" msgid="3183692287397645708">"回车"</string>
     <string name="spoken_description_search" msgid="5099937658231911288">"搜索"</string>
     <string name="spoken_description_dot" msgid="5644176501632325560">"点"</string>
     <string name="spoken_description_language_switch" msgid="6818666779313544553">"切换语言"</string>
     <string name="spoken_description_action_next" msgid="431761808119616962">"下一个"</string>
     <string name="spoken_description_action_previous" msgid="2919072174697865110">"上一个"</string>
-    <string name="spoken_description_shiftmode_on" msgid="5107180516341258979">"Shift模式已启用"</string>
-    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"大写锁定模式已启用"</string>
-    <string name="spoken_description_shiftmode_off" msgid="5039126122829961331">"Shift模式已停用"</string>
+    <string name="spoken_description_shiftmode_on" msgid="5107180516341258979">"已开启Shift模式"</string>
+    <string name="spoken_description_shiftmode_locked" msgid="7307477738053606881">"已锁定大写模式"</string>
+    <string name="spoken_description_shiftmode_off" msgid="5039126122829961331">"已关闭Shift模式"</string>
     <string name="spoken_description_mode_symbol" msgid="111186851131446691">"符号模式"</string>
     <string name="spoken_description_mode_alpha" msgid="4676004119618778911">"字母模式"</string>
     <string name="spoken_description_mode_phone" msgid="2061220553756692903">"电话模式"</string>
@@ -62,9 +62,9 @@
     <string name="keyboard_mode_text" msgid="9138789594969187494">"文字"</string>
     <string name="keyboard_mode_time" msgid="8558297845514402675">"时间"</string>
     <string name="keyboard_mode_url" msgid="8072011652949962550">"网址"</string>
-    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"最近使用"</string>
+    <string name="spoken_descrption_emoji_category_recents" msgid="4185344945205590692">"最近用过"</string>
     <string name="spoken_descrption_emoji_category_people" msgid="8414196269847492817">"人物"</string>
-    <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"物体"</string>
+    <string name="spoken_descrption_emoji_category_objects" msgid="6116297906606195278">"物件"</string>
     <string name="spoken_descrption_emoji_category_nature" msgid="5018340512472354640">"自然"</string>
     <string name="spoken_descrption_emoji_category_places" msgid="1163315840948545317">"地点"</string>
     <string name="spoken_descrption_emoji_category_symbols" msgid="474680659024880601">"符号"</string>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
index 063f211..720cf6b 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java
@@ -19,15 +19,12 @@
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
 import android.support.v4.view.accessibility.AccessibilityRecordCompat;
 import android.util.Log;
-import android.util.SparseArray;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.inputmethod.EditorInfo;
@@ -37,9 +34,10 @@
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.settings.SettingsValues;
-import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 
+import java.util.List;
+
 /**
  * Exposes a virtual view sub-tree for {@link KeyboardView} and generates
  * {@link AccessibilityEvent}s for individual {@link Key}s.
@@ -58,9 +56,6 @@
     private final KeyCodeDescriptionMapper mKeyCodeDescriptionMapper;
     private final AccessibilityUtils mAccessibilityUtils;
 
-    /** A map of integer IDs to {@link Key}s. */
-    private final SparseArray<Key> mVirtualViewIdToKey = CollectionUtils.newSparseArray();
-
     /** Temporary rect used to calculate in-screen bounds. */
     private final Rect mTempBoundsInScreen = new Rect();
 
@@ -73,6 +68,9 @@
     /** The current keyboard view. */
     private KeyboardView mKeyboardView;
 
+    /** The current keyboard. */
+    private Keyboard mKeyboard;
+
     public AccessibilityEntityProvider(final KeyboardView keyboardView,
             final InputMethodService inputMethod) {
         mInputMethodService = inputMethod;
@@ -92,14 +90,43 @@
 
         // Since this class is constructed lazily, we might not get a subsequent
         // call to setKeyboard() and therefore need to call it now.
-        setKeyboard();
+        setKeyboard(keyboardView.getKeyboard());
     }
 
     /**
      * Sets the keyboard represented by this node provider.
+     *
+     * @param keyboard The keyboard that is being set to the keyboard view.
      */
-    public void setKeyboard() {
-        assignVirtualViewIds();
+    public void setKeyboard(final Keyboard keyboard) {
+        mKeyboard = keyboard;
+    }
+
+    private Key getKeyOf(final int virtualViewId) {
+        if (mKeyboard == null) {
+            return null;
+        }
+        final List<Key> sortedKeys = mKeyboard.getSortedKeys();
+        // Use a virtual view id as an index of the sorted keys list.
+        if (virtualViewId >= 0 && virtualViewId < sortedKeys.size()) {
+            return sortedKeys.get(virtualViewId);
+        }
+        return null;
+    }
+
+    private int getVirtualViewIdOf(final Key key) {
+        if (mKeyboard == null) {
+            return View.NO_ID;
+        }
+        final List<Key> sortedKeys = mKeyboard.getSortedKeys();
+        final int size = sortedKeys.size();
+        for (int index = 0; index < size; index++) {
+            if (sortedKeys.get(index) == key) {
+                // Use an index of the sorted keys list as a virtual view id.
+                return index;
+            }
+        }
+        return View.NO_ID;
     }
 
     /**
@@ -112,7 +139,7 @@
      * @see AccessibilityEvent
      */
     public AccessibilityEvent createAccessibilityEvent(final Key key, final int eventType) {
-        final int virtualViewId = generateVirtualViewIdForKey(key);
+        final int virtualViewId = getVirtualViewIdOf(key);
         final String keyDescription = getKeyDescription(key);
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         event.setPackageName(mKeyboardView.getContext().getPackageName());
@@ -158,16 +185,21 @@
             ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo);
 
             // Add the virtual children of the root View.
-            final Keyboard keyboard = mKeyboardView.getKeyboard();
-            for (final Key key : keyboard.getSortedKeys()) {
-                final int childVirtualViewId = generateVirtualViewIdForKey(key);
-                rootInfo.addChild(mKeyboardView, childVirtualViewId);
+            final List<Key> sortedKeys = mKeyboard.getSortedKeys();
+            final int size = sortedKeys.size();
+            for (int index = 0; index < size; index++) {
+                final Key key = sortedKeys.get(index);
+                if (key.isSpacer()) {
+                    continue;
+                }
+                // Use an index of the sorted keys list as a virtual view id.
+                rootInfo.addChild(mKeyboardView, index);
             }
             return rootInfo;
         }
 
-        // Find the view that corresponds to the given id.
-        final Key key = mVirtualViewIdToKey.get(virtualViewId);
+        // Find the key that corresponds to the given virtual view id.
+        final Key key = getKeyOf(virtualViewId);
         if (key == null) {
             Log.e(TAG, "Invalid virtual view ID: " + virtualViewId);
             return null;
@@ -202,31 +234,10 @@
         return info;
     }
 
-    /**
-     * Simulates a key press by injecting touch events into the keyboard view.
-     * This avoids the complexity of trackers and listeners within the keyboard.
-     *
-     * @param key The key to press.
-     */
-    void simulateKeyPress(final Key key) {
-        final int x = key.getHitBox().centerX();
-        final int y = key.getHitBox().centerY();
-        final long downTime = SystemClock.uptimeMillis();
-        final MotionEvent downEvent = MotionEvent.obtain(
-                downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
-        final MotionEvent upEvent = MotionEvent.obtain(
-                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
-
-        mKeyboardView.onTouchEvent(downEvent);
-        mKeyboardView.onTouchEvent(upEvent);
-        downEvent.recycle();
-        upEvent.recycle();
-    }
-
     @Override
     public boolean performAction(final int virtualViewId, final int action,
             final Bundle arguments) {
-        final Key key = mVirtualViewIdToKey.get(virtualViewId);
+        final Key key = getKeyOf(virtualViewId);
         if (key == null) {
             return false;
         }
@@ -242,7 +253,7 @@
      * @return The result of performing the action, or false if the action is not supported.
      */
     boolean performActionForKey(final Key key, final int action, final Bundle arguments) {
-        final int virtualViewId = generateVirtualViewIdForKey(key);
+        final int virtualViewId = getVirtualViewIdOf(key);
 
         switch (action) {
         case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
@@ -288,7 +299,7 @@
         final boolean shouldObscure = mAccessibilityUtils.shouldObscureInput(editorInfo);
         final SettingsValues currentSettings = Settings.getInstance().getCurrent();
         final String keyCodeDescription = mKeyCodeDescriptionMapper.getDescriptionForKey(
-                mKeyboardView.getContext(), mKeyboardView.getKeyboard(), key, shouldObscure);
+                mKeyboardView.getContext(), mKeyboard, key, shouldObscure);
         if (currentSettings.isWordSeparator(key.getCode())) {
             return mAccessibilityUtils.getAutoCorrectionDescription(
                     keyCodeDescription, shouldObscure);
@@ -298,39 +309,9 @@
     }
 
     /**
-     * Assigns virtual view IDs to keyboard keys and populates the related maps.
-     */
-    private void assignVirtualViewIds() {
-        final Keyboard keyboard = mKeyboardView.getKeyboard();
-        if (keyboard == null) {
-            return;
-        }
-        mVirtualViewIdToKey.clear();
-
-        for (final Key key : keyboard.getSortedKeys()) {
-            final int virtualViewId = generateVirtualViewIdForKey(key);
-            mVirtualViewIdToKey.put(virtualViewId, key);
-        }
-    }
-
-    /**
      * Updates the parent's on-screen location.
      */
     private void updateParentLocation() {
         mKeyboardView.getLocationOnScreen(mParentLocation);
     }
-
-    /**
-     * Generates a virtual view identifier for the given key. Returned
-     * identifiers are valid until the next global layout state change.
-     *
-     * @param key The key to identify.
-     * @return A virtual view identifier.
-     */
-    private static int generateVirtualViewIdForKey(final Key key) {
-        // The key x- and y-coordinates are stable between layout changes.
-        // Generate an identifier by bit-shifting the x-coordinate to the
-        // left-half of the integer and OR'ing with the y-coordinate.
-        return ((0xFFFF & key.getX()) << (Integer.SIZE / 2)) | (0xFFFF & key.getY());
-    }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index 0043b78..d2031d1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.inputmethodservice.InputMethodService;
+import android.os.SystemClock;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
@@ -34,6 +35,7 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.MainKeyboardView;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
 
 public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
@@ -55,6 +57,7 @@
 
     private InputMethodService mInputMethod;
     private MainKeyboardView mView;
+    private Keyboard mKeyboard;
     private AccessibilityEntityProvider mAccessibilityNodeProvider;
 
     private Key mLastHoverKey = null;
@@ -65,7 +68,8 @@
     private int mEdgeSlop;
 
     /** The most recently set keyboard mode. */
-    private int mLastKeyboardMode;
+    private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
+    private static final int KEYBOARD_IS_HIDDEN = -1;
 
     public static void init(final InputMethodService inputMethod) {
         sInstance.initInternal(inputMethod);
@@ -104,6 +108,10 @@
             return;
         }
         mAccessibilityNodeProvider.setView(view);
+
+        // Since this class is constructed lazily, we might not get a subsequent
+        // call to setKeyboard() and therefore need to call it now.
+        setKeyboard(view.getKeyboard());
     }
 
     /**
@@ -111,24 +119,33 @@
      * <p>
      * <b>Note:</b> This method will be called even if accessibility is not
      * enabled.
+     * @param keyboard The keyboard that is being set to the wrapping view.
      */
-    public void setKeyboard() {
-        if (mView == null) {
+    public void setKeyboard(final Keyboard keyboard) {
+        if (keyboard == null) {
             return;
         }
         if (mAccessibilityNodeProvider != null) {
-            mAccessibilityNodeProvider.setKeyboard();
+            mAccessibilityNodeProvider.setKeyboard(keyboard);
         }
-        final int keyboardMode = mView.getKeyboard().mId.mMode;
+        final Keyboard lastKeyboard = mKeyboard;
+        final int lastKeyboardMode = mLastKeyboardMode;
+        mKeyboard = keyboard;
+        mLastKeyboardMode = keyboard.mId.mMode;
 
         // Since this method is called even when accessibility is off, make sure
-        // to check the state before announcing anything. Also, don't announce
-        // changes within the same mode.
-        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()
-                && (mLastKeyboardMode != keyboardMode)) {
-            announceKeyboardMode(keyboardMode);
+        // to check the state before announcing anything.
+        if (!AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            return;
         }
-        mLastKeyboardMode = keyboardMode;
+        // Announce the language name only when the language is changed.
+        if (lastKeyboard == null || !lastKeyboard.mId.mSubtype.equals(keyboard.mId.mSubtype)) {
+            announceKeyboardLanguage(keyboard);
+        }
+        // Announce the mode only when the mode is changed.
+        if (lastKeyboardMode != keyboard.mId.mMode) {
+            announceKeyboardMode(keyboard);
+        }
     }
 
     /**
@@ -139,23 +156,35 @@
             return;
         }
         announceKeyboardHidden();
-        mLastKeyboardMode = -1;
+        mLastKeyboardMode = KEYBOARD_IS_HIDDEN;
     }
 
     /**
-     * Announces which type of keyboard is being displayed. If the keyboard type
-     * is unknown, no announcement is made.
+     * Announces which language of keyboard is being displayed.
      *
-     * @param mode The new keyboard mode.
+     * @param keyboard The new keyboard.
      */
-    private void announceKeyboardMode(int mode) {
-        final int resId = KEYBOARD_MODE_RES_IDS.get(mode);
-        if (resId == 0) {
+    private void announceKeyboardLanguage(final Keyboard keyboard) {
+        final String languageText = SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(
+                keyboard.mId.mSubtype);
+        sendWindowStateChanged(languageText);
+    }
+
+    /**
+     * Announces which type of keyboard is being displayed.
+     * If the keyboard type is unknown, no announcement is made.
+     *
+     * @param keyboard The new keyboard.
+     */
+    private void announceKeyboardMode(final Keyboard keyboard) {
+        final int mode = keyboard.mId.mMode;
+        final Context context = mView.getContext();
+        final int modeTextResId = KEYBOARD_MODE_RES_IDS.get(mode);
+        if (modeTextResId == 0) {
             return;
         }
-        final Context context = mView.getContext();
-        final String keyboardMode = context.getString(resId);
-        final String text = context.getString(R.string.announce_keyboard_mode, keyboardMode);
+        final String modeText = context.getString(modeTextResId);
+        final String text = context.getString(R.string.announce_keyboard_mode, modeText);
         sendWindowStateChanged(text);
     }
 
@@ -233,7 +262,8 @@
             // Make sure we're not getting an EXIT event because the user slid
             // off the keyboard area, then force a key press.
             if (key != null) {
-                getAccessibilityNodeProvider().simulateKeyPress(key);
+                final long downTime = simulateKeyPress(key);
+                simulateKeyRelease(key, downTime);
             }
             //$FALL-THROUGH$
         case MotionEvent.ACTION_HOVER_ENTER:
@@ -274,6 +304,38 @@
     }
 
     /**
+     * Simulates a key press by injecting touch event into the keyboard view.
+     * This avoids the complexity of trackers and listeners within the keyboard.
+     *
+     * @param key The key to press.
+     */
+    private long simulateKeyPress(final Key key) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
+        final long downTime = SystemClock.uptimeMillis();
+        final MotionEvent downEvent = MotionEvent.obtain(
+                downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0);
+        mView.onTouchEvent(downEvent);
+        downEvent.recycle();
+        return downTime;
+    }
+
+    /**
+     * Simulates a key release by injecting touch event into the keyboard view.
+     * This avoids the complexity of trackers and listeners within the keyboard.
+     *
+     * @param key The key to release.
+     */
+    private void simulateKeyRelease(final Key key, final long downTime) {
+        final int x = key.getHitBox().centerX();
+        final int y = key.getHitBox().centerY();
+        final MotionEvent upEvent = MotionEvent.obtain(
+                downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0);
+        mView.onTouchEvent(upEvent);
+        upEvent.recycle();
+    }
+
+    /**
      * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key,
      * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key.
      *
@@ -329,12 +391,11 @@
      * Notifies the user of changes in the keyboard shift state.
      */
     public void notifyShiftState() {
-        if (mView == null) {
+        if (mView == null || mKeyboard == null) {
             return;
         }
 
-        final Keyboard keyboard = mView.getKeyboard();
-        final KeyboardId keyboardId = keyboard.mId;
+        final KeyboardId keyboardId = mKeyboard.mId;
         final int elementId = keyboardId.mElementId;
         final Context context = mView.getContext();
         final CharSequence text;
@@ -359,14 +420,13 @@
      * Notifies the user of changes in the keyboard symbols state.
      */
     public void notifySymbolsState() {
-        if (mView == null) {
+        if (mView == null || mKeyboard == null) {
             return;
         }
 
-        final Keyboard keyboard = mView.getKeyboard();
-        final Context context = mView.getContext();
-        final KeyboardId keyboardId = keyboard.mId;
+        final KeyboardId keyboardId = mKeyboard.mId;
         final int elementId = keyboardId.mElementId;
+        final Context context = mView.getContext();
         final int resId;
 
         switch (elementId) {
@@ -388,12 +448,9 @@
             resId = R.string.spoken_description_mode_phone_shift;
             break;
         default:
-            resId = -1;
-        }
-
-        if (resId < 0) {
             return;
         }
+
         final String text = context.getString(resId);
         AccessibilityUtils.getInstance().announceForAccessibility(mView, text);
     }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 1cafd41..ecef8cc 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -406,7 +406,7 @@
 
         // This always needs to be set since the accessibility state can
         // potentially change without the keyboard being set again.
-        AccessibleKeyboardViewProxy.getInstance().setKeyboard();
+        AccessibleKeyboardViewProxy.getInstance().setKeyboard(keyboard);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
index 3746d5b..c89bda4 100644
--- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
+++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java
@@ -118,10 +118,11 @@
         final int[] proximityCharsArray = new int[mGridSize * MAX_PROXIMITY_CHARS_SIZE];
         Arrays.fill(proximityCharsArray, Constants.NOT_A_CODE);
         for (int i = 0; i < mGridSize; ++i) {
-            final int proximityCharsLength = gridNeighborKeys[i].size();
+            final List<Key> neighborKeys = gridNeighborKeys[i];
+            final int proximityCharsLength = neighborKeys.size();
             int infoIndex = i * MAX_PROXIMITY_CHARS_SIZE;
             for (int j = 0; j < proximityCharsLength; ++j) {
-                final Key neighborKey = gridNeighborKeys[i].get(j);
+                final Key neighborKey = neighborKeys.get(j);
                 // Excluding from proximityCharsArray
                 if (!needsProximityInfo(neighborKey)) {
                     continue;
@@ -359,11 +360,11 @@
         for (int i = 0; i < gridSize; ++i) {
             final int indexStart = i * keyCount;
             final int indexEnd = indexStart + neighborCountPerCell[i];
-            final ArrayList<Key> neighbords = CollectionUtils.newArrayList(indexEnd - indexStart);
+            final ArrayList<Key> neighbors = CollectionUtils.newArrayList(indexEnd - indexStart);
             for (int index = indexStart; index < indexEnd; index++) {
-                neighbords.add(neighborsFlatBuffer[index]);
+                neighbors.add(neighborsFlatBuffer[index]);
             }
-            mGridNeighbors[i] = Collections.unmodifiableList(neighbords);
+            mGridNeighbors[i] = Collections.unmodifiableList(neighbors);
         }
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
index 397c098..67a2227 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DynamicGridKeyboard.java
@@ -213,8 +213,7 @@
             if (mCachedGridKeys != null) {
                 return mCachedGridKeys;
             }
-            final ArrayList<Key> cachedKeys = CollectionUtils.newArrayList(mGridKeys.size());
-            cachedKeys.addAll(mGridKeys);
+            final ArrayList<Key> cachedKeys = new ArrayList<Key>(mGridKeys);
             mCachedGridKeys = Collections.unmodifiableList(cachedKeys);
             return mCachedGridKeys;
         }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
index 0b6258a..fa58fb0 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java
@@ -69,78 +69,74 @@
         public final Locale mLocale;
         public final ConcurrentHashMap<String, Dictionary> mDictMap =
                 CollectionUtils.newConcurrentHashMap();
-        // Main dictionary will be asynchronously loaded.
-        public Dictionary mMainDictionary;
+        public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap =
+                CollectionUtils.newConcurrentHashMap();
+        // TODO: Remove sub dictionary members and use mSubDictMap.
         public final ContactsBinaryDictionary mContactsDictionary;
         public final UserBinaryDictionary mUserDictionary;
-        public final UserHistoryDictionary mUserHistoryDictionary;
         public final PersonalizationDictionary mPersonalizationDictionary;
 
         public Dictionaries() {
             mLocale = null;
-            mMainDictionary = null;
             mContactsDictionary = null;
             mUserDictionary = null;
-            mUserHistoryDictionary = null;
             mPersonalizationDictionary = null;
         }
 
         public Dictionaries(final Locale locale, final Dictionary mainDict,
             final ContactsBinaryDictionary contactsDict, final UserBinaryDictionary userDict,
-            final UserHistoryDictionary userHistoryDict,
+            final ExpandableBinaryDictionary userHistoryDict,
             final PersonalizationDictionary personalizationDict) {
             mLocale = locale;
+            // Main dictionary can be asynchronously loaded.
             setMainDict(mainDict);
             mContactsDictionary = contactsDict;
-            if (mContactsDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_CONTACTS, mContactsDictionary);
-            }
+            setSubDict(Dictionary.TYPE_CONTACTS, mContactsDictionary);
             mUserDictionary = userDict;
-            if (mUserDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_USER, mUserDictionary);
-            }
-            mUserHistoryDictionary = userHistoryDict;
-            if (mUserHistoryDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_USER_HISTORY, mUserHistoryDictionary);
-            }
+            setSubDict(Dictionary.TYPE_USER, mUserDictionary);
+            setSubDict(Dictionary.TYPE_USER_HISTORY, userHistoryDict);
             mPersonalizationDictionary = personalizationDict;
-            if (mPersonalizationDictionary != null) {
-                mDictMap.put(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary);
+            setSubDict(Dictionary.TYPE_PERSONALIZATION, mPersonalizationDictionary);
+        }
+
+        private void setSubDict(final String dictType, final ExpandableBinaryDictionary dict) {
+            if (dict != null) {
+                mDictMap.put(dictType, dict);
+                mSubDictMap.put(dictType, dict);
             }
         }
 
         public void setMainDict(final Dictionary mainDict) {
-            mMainDictionary = mainDict;
             // Close old dictionary if exists. Main dictionary can be assigned multiple times.
             final Dictionary oldDict;
-            if (mMainDictionary != null) {
-                oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mMainDictionary);
+            if (mainDict != null) {
+                oldDict = mDictMap.put(Dictionary.TYPE_MAIN, mainDict);
             } else {
                 oldDict = mDictMap.remove(Dictionary.TYPE_MAIN);
             }
-            if (oldDict != null && mMainDictionary != oldDict) {
+            if (oldDict != null && mainDict != oldDict) {
                 oldDict.close();
             }
         }
 
-        public boolean hasMainDict() {
-            return mMainDictionary != null;
+        public Dictionary getMainDict() {
+            return mDictMap.get(Dictionary.TYPE_MAIN);
         }
 
-        public boolean hasContactsDict() {
-            return mContactsDictionary != null;
+        public ExpandableBinaryDictionary getSubDict(final String dictType) {
+            return mSubDictMap.get(dictType);
         }
 
-        public boolean hasUserDict() {
-            return mUserDictionary != null;
+        public boolean hasDict(final String dictType) {
+            return mDictMap.contains(dictType);
         }
 
-        public boolean hasUserHistoryDict() {
-            return mUserHistoryDictionary != null;
-        }
-
-        public boolean hasPersonalizationDict() {
-            return mPersonalizationDictionary != null;
+        public void closeDict(final String dictType) {
+            final Dictionary dict = mDictMap.remove(dictType);
+            mSubDictMap.remove(dictType);
+            if (dict != null) {
+                dict.close();
+            }
         }
     }
 
@@ -172,12 +168,12 @@
             // The main dictionary will be asynchronously loaded.
             newMainDict = null;
         } else {
-            newMainDict = mDictionaries.mMainDictionary;
+            newMainDict = mDictionaries.getMainDict();
         }
 
         // Open or move contacts dictionary.
         final ContactsBinaryDictionary newContactsDict;
-        if (!closeContactsDictionary && mDictionaries.hasContactsDict()) {
+        if (!closeContactsDictionary && mDictionaries.hasDict(Dictionary.TYPE_CONTACTS)) {
             newContactsDict = mDictionaries.mContactsDictionary;
         } else if (useContactsDict) {
             newContactsDict = new ContactsBinaryDictionary(context, newLocale);
@@ -187,16 +183,16 @@
 
         // Open or move user dictionary.
         final UserBinaryDictionary newUserDictionary;
-        if (!closeUserDictionary && mDictionaries.hasUserDict()) {
+        if (!closeUserDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER)) {
             newUserDictionary = mDictionaries.mUserDictionary;
         } else {
             newUserDictionary = new UserBinaryDictionary(context, newLocale);
         }
 
         // Open or move user history dictionary.
-        final UserHistoryDictionary newUserHistoryDict;
-        if (!closeUserHistoryDictionary && mDictionaries.hasUserHistoryDict()) {
-            newUserHistoryDict = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary newUserHistoryDict;
+        if (!closeUserHistoryDictionary && mDictionaries.hasDict(Dictionary.TYPE_USER_HISTORY)) {
+            newUserHistoryDict = mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         } else if (usePersonalizedDicts) {
             newUserHistoryDict = PersonalizationHelper.getUserHistoryDictionary(context, newLocale);
         } else {
@@ -205,7 +201,8 @@
 
         // Open or move personalization dictionary.
         final PersonalizationDictionary newPersonalizationDict;
-        if (!closePersonalizationDictionary && mDictionaries.hasPersonalizationDict()) {
+        if (!closePersonalizationDictionary
+                && mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION)) {
             newPersonalizationDict = mDictionaries.mPersonalizationDictionary;
         } else if (usePersonalizedDicts) {
             newPersonalizationDict =
@@ -217,9 +214,6 @@
         // Replace Dictionaries.
         final Dictionaries newDictionaries = new Dictionaries(newLocale, newMainDict,
                 newContactsDict,  newUserDictionary, newUserHistoryDict, newPersonalizationDict);
-        if (listener != null) {
-            listener.onUpdateMainDictionaryAvailability(newDictionaries.hasMainDict());
-        }
         final Dictionaries oldDictionaries;
         synchronized (mLock) {
             oldDictionaries = mDictionaries;
@@ -228,24 +222,28 @@
                 asyncReloadMainDictionary(context, newLocale, listener);
             }
         }
+        if (listener != null) {
+            listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
+        }
 
         // Clean up old dictionaries.
+        if (reloadMainDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_MAIN);
+        }
+        if (closeContactsDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_CONTACTS);
+        }
+        if (closeUserDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_USER);
+        }
+        if (closeUserHistoryDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_USER_HISTORY);
+        }
+        if (closePersonalizationDictionary) {
+            oldDictionaries.closeDict(Dictionary.TYPE_PERSONALIZATION);
+        }
         oldDictionaries.mDictMap.clear();
-        if (reloadMainDictionary && oldDictionaries.hasMainDict()) {
-            oldDictionaries.mMainDictionary.close();
-        }
-        if (closeContactsDictionary && oldDictionaries.hasContactsDict()) {
-            oldDictionaries.mContactsDictionary.close();
-        }
-        if (closeUserDictionary && oldDictionaries.hasUserDict()) {
-            oldDictionaries.mUserDictionary.close();
-        }
-        if (closeUserHistoryDictionary && oldDictionaries.hasUserHistoryDict()) {
-            oldDictionaries.mUserHistoryDictionary.close();
-        }
-        if (closePersonalizationDictionary && oldDictionaries.hasPersonalizationDict()) {
-            oldDictionaries.mPersonalizationDictionary.close();
-        }
+        oldDictionaries.mSubDictMap.clear();
     }
 
     private void asyncReloadMainDictionary(final Context context, final Locale locale,
@@ -266,7 +264,7 @@
                     }
                 }
                 if (listener != null) {
-                    listener.onUpdateMainDictionaryAvailability(mDictionaries.hasMainDict());
+                    listener.onUpdateMainDictionaryAvailability(hasInitializedMainDictionary());
                 }
                 latchForWaitingLoadingMainDictionary.countDown();
             }
@@ -332,39 +330,27 @@
             dictionaries = mDictionaries;
             mDictionaries = new Dictionaries();
         }
-        if (dictionaries.hasMainDict()) {
-            dictionaries.mMainDictionary.close();
-        }
-        if (dictionaries.hasContactsDict()) {
-            dictionaries.mContactsDictionary.close();
-        }
-        if (dictionaries.hasUserDict()) {
-            dictionaries.mUserDictionary.close();
-        }
-        if (dictionaries.hasUserHistoryDict()) {
-            dictionaries.mUserHistoryDictionary.close();
-        }
-        if (dictionaries.hasPersonalizationDict()) {
-            dictionaries.mPersonalizationDictionary.close();
+        for (final Dictionary dict : dictionaries.mDictMap.values()) {
+            dict.close();
         }
     }
 
     // The main dictionary could have been loaded asynchronously.  Don't cache the return value
     // of this method.
     public boolean hasInitializedMainDictionary() {
-        final Dictionaries dictionaries = mDictionaries;
-        return dictionaries.hasMainDict() && dictionaries.mMainDictionary.isInitialized();
+        final Dictionary mainDict = mDictionaries.getMainDict();
+        return mainDict != null &&  mainDict.isInitialized();
     }
 
     public boolean hasPersonalizationDictionary() {
-        return mDictionaries.hasPersonalizationDict();
+        return mDictionaries.hasDict(Dictionary.TYPE_PERSONALIZATION);
     }
 
     public void flushPersonalizationDictionary() {
         final PersonalizationDictionary personalizationDict =
                 mDictionaries.mPersonalizationDictionary;
         if (personalizationDict != null) {
-            personalizationDict.flush();
+            personalizationDict.asyncFlushBinaryDictionary();
         }
     }
 
@@ -377,18 +363,9 @@
     public void waitForLoadingDictionariesForTesting(final long timeout, final TimeUnit unit)
             throws InterruptedException {
         waitForLoadingMainDictionary(timeout, unit);
-        final Dictionaries dictionaries = mDictionaries;
-        if (dictionaries.hasContactsDict()) {
-            dictionaries.mContactsDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasUserDict()) {
-            dictionaries.mUserDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasUserHistoryDict()) {
-            dictionaries.mUserHistoryDictionary.waitAllTasksForTests();
-        }
-        if (dictionaries.hasPersonalizationDict()) {
-            dictionaries.mPersonalizationDictionary.waitAllTasksForTests();
+        final Map<String, ExpandableBinaryDictionary> dictMap = mDictionaries.mSubDictMap;
+        for (final ExpandableBinaryDictionary dict : dictMap.values()) {
+            dict.waitAllTasksForTests();
         }
     }
 
@@ -411,7 +388,9 @@
     public void addToUserHistory(final String suggestion, final boolean wasAutoCapitalized,
             final String previousWord, final int timeStampInSeconds) {
         final Dictionaries dictionaries = mDictionaries;
-        if (!dictionaries.hasUserHistoryDict()) {
+        final ExpandableBinaryDictionary userHistoryDictionary =
+                dictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
+        if (userHistoryDictionary != null) {
             return;
         }
         final int maxFreq = getMaxFrequency(suggestion);
@@ -439,8 +418,8 @@
             // History dictionary in order to avoid suggesting them until the dictionary
             // consolidation is done.
             // TODO: Remove this hack when ready.
-            final int lowerCaseFreqInMainDict = dictionaries.hasMainDict() ?
-                    dictionaries.mMainDictionary.getFrequency(suggestionLowerCase) :
+            final int lowerCaseFreqInMainDict = dictionaries.hasDict(Dictionary.TYPE_MAIN) ?
+                    dictionaries.getMainDict().getFrequency(suggestionLowerCase) :
                             Dictionary.NOT_A_PROBABILITY;
             if (maxFreq < lowerCaseFreqInMainDict
                     && lowerCaseFreqInMainDict >= CAPITALIZED_FORM_MAX_PROBABILITY_FOR_INSERT) {
@@ -453,14 +432,15 @@
         // We demote unrecognized words (frequency < 0, below) by specifying them as "invalid".
         // We don't add words with 0-frequency (assuming they would be profanity etc.).
         final boolean isValid = maxFreq > 0;
-        dictionaries.mUserHistoryDictionary.addToDictionary(
-                previousWord, secondWord, isValid, timeStampInSeconds);
+        UserHistoryDictionary.addToDictionary(userHistoryDictionary, previousWord, secondWord,
+                isValid, timeStampInSeconds);
     }
 
     public void cancelAddingUserHistory(final String previousWord, final String committedWord) {
-        final UserHistoryDictionary userHistoryDictionary = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary userHistoryDictionary =
+                mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDictionary != null) {
-            userHistoryDictionary.cancelAddingUserHistory(previousWord, committedWord);
+            userHistoryDictionary.removeBigramDynamically(previousWord, committedWord);
         }
     }
 
@@ -492,10 +472,10 @@
 
     public boolean isValidMainDictWord(final String word) {
         final Dictionaries dictionaries = mDictionaries;
-        if (TextUtils.isEmpty(word) || !dictionaries.hasMainDict()) {
+        if (TextUtils.isEmpty(word) || !dictionaries.hasDict(Dictionary.TYPE_MAIN)) {
             return false;
         }
-        return dictionaries.mMainDictionary.isValidWord(word);
+        return dictionaries.getMainDict().isValidWord(word);
     }
 
     public boolean isValidWord(final String word, final boolean ignoreCase) {
@@ -538,22 +518,23 @@
 
 
     public void clearUserHistoryDictionary() {
-        final UserHistoryDictionary userHistoryDict = mDictionaries.mUserHistoryDictionary;
+        final ExpandableBinaryDictionary userHistoryDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
         if (userHistoryDict == null) {
             return;
         }
-        userHistoryDict.clearAndFlushDictionary();
+        userHistoryDict.clear();
     }
 
     // This method gets called only when the IME receives a notification to remove the
     // personalization dictionary.
     public void clearPersonalizationDictionary() {
-        final PersonalizationDictionary personalizationDict =
-                mDictionaries.mPersonalizationDictionary;
+        final ExpandableBinaryDictionary personalizationDict =
+                mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION);
         if (personalizationDict == null) {
             return;
         }
-        personalizationDict.clearAndFlushDictionary();
+        personalizationDict.clear();
     }
 
     public void addMultipleDictionaryEntriesToPersonalizationDictionary(
@@ -571,18 +552,7 @@
     }
 
     public void dumpDictionaryForDebug(final String dictName) {
-        final ExpandableBinaryDictionary dictToDump;
-        if (dictName.equals(Dictionary.TYPE_CONTACTS)) {
-            dictToDump = mDictionaries.mContactsDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_USER)) {
-            dictToDump = mDictionaries.mUserDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_USER_HISTORY)) {
-            dictToDump = mDictionaries.mUserHistoryDictionary;
-        } else if (dictName.equals(Dictionary.TYPE_PERSONALIZATION)) {
-            dictToDump = mDictionaries.mPersonalizationDictionary;
-        } else {
-            dictToDump = null;
-        }
+        final ExpandableBinaryDictionary dictToDump = mDictionaries.getSubDict(dictName);
         if (dictToDump == null) {
             Log.e(TAG, "Cannot dump " + dictName + ". "
                     + "The dictionary is not being used for suggestion or cannot be dumped.");
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 8431838..1adf349 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -244,7 +244,7 @@
                 DICTIONARY_FORMAT_VERSION, getHeaderAttributeMap());
     }
 
-    protected void clear() {
+    public void clear() {
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
             public void run() {
@@ -297,7 +297,7 @@
     /**
      * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry.
      */
-    protected void addWordDynamically(final String word, final int frequency,
+    public void addWordDynamically(final String word, final int frequency,
             final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord,
             final boolean isBlacklisted, final int timestamp) {
         reloadDictionaryIfRequired();
@@ -324,7 +324,7 @@
     /**
      * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry.
      */
-    protected void addBigramDynamically(final String word0, final String word1,
+    public void addBigramDynamically(final String word0, final String word1,
             final int frequency, final int timestamp) {
         reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
@@ -347,7 +347,7 @@
     /**
      * Dynamically remove a word bigram in the dictionary.
      */
-    protected void removeBigramDynamically(final String word0, final String word1) {
+    public void removeBigramDynamically(final String word0, final String word1) {
         reloadDictionaryIfRequired();
         ExecutorUtils.getExecutor(mDictName).execute(new Runnable() {
             @Override
@@ -632,7 +632,7 @@
     /**
      * Flush binary dictionary to dictionary file.
      */
-    protected void asyncFlushBinaryDictionary() {
+    public void asyncFlushBinaryDictionary() {
         final Runnable newTask = new Runnable() {
             @Override
             public void run() {
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index f1f9060..d2100d4 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -451,8 +451,10 @@
                 break;
             case Constants.CODE_SHIFT_ENTER:
                 // TODO: remove this object
+                final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER,
+                        event.mKeyCode, event.mX, event.mY, event.isKeyRepeat());
                 final InputTransaction tmpTransaction = new InputTransaction(
-                        inputTransaction.mSettingsValues, inputTransaction.mEvent,
+                        inputTransaction.mSettingsValues, tmpEvent,
                         inputTransaction.mTimestamp, inputTransaction.mSpaceState,
                         inputTransaction.mShiftState);
                 didAutoCorrect = handleNonSpecialCharacter(tmpTransaction, handler);
diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
index 712e314..35b4ccd 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java
@@ -65,12 +65,8 @@
             dumpAllWordsForDebug();
         }
         // Flush pending writes.
-        flush();
-        super.close();
-    }
-
-    public void flush() {
         asyncFlushBinaryDictionary();
+        super.close();
     }
 
     @Override
@@ -103,51 +99,16 @@
         addMultipleDictionaryEntriesDynamically(languageModelParams, callback);
     }
 
-    /**
-     * Pair will be added to the decaying dictionary.
-     *
-     * The first word may be null. That means we don't know the context, in other words,
-     * it's only a unigram. The first word may also be an empty string : this means start
-     * context, as in beginning of a sentence for example.
-     * The second word may not be null (a NullPointerException would be thrown).
-     */
-    public void addToDictionary(final String word0, final String word1, final boolean isValid,
-            final int timestamp) {
-        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
-                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
-            return;
-        }
-        final int frequency = isValid ?
-                FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
-        addWordDynamically(word1, frequency, null /* shortcutTarget */, 0 /* shortcutFreq */,
-                false /* isNotAWord */, false /* isBlacklisted */, timestamp);
-        // Do not insert a word as a bigram of itself
-        if (word1.equals(word0)) {
-            return;
-        }
-        if (null != word0) {
-            addBigramDynamically(word0, word1, frequency, timestamp);
-        }
-    }
-
     @Override
     protected void loadInitialContentsLocked() {
         // No initial contents.
     }
 
     @UsedForTesting
-    public void clearAndFlushDictionary() {
-        // Clear the node structure on memory
-        clear();
-        // Then flush the cleared state of the dictionary on disk.
-        asyncFlushBinaryDictionary();
-    }
-
-    @UsedForTesting
     public void clearAndFlushDictionaryWithAdditionalAttributes(
             final Map<String, String> attributeMap) {
         mAdditionalAttributeMap = attributeMap;
-        clearAndFlushDictionary();
+        clear();
     }
 
     /* package */ void runGCIfRequired() {
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
index 385b525..7c43182 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java
@@ -135,7 +135,7 @@
                 if (entry.getValue() != null) {
                     final DecayingExpandableBinaryDictionaryBase dict = entry.getValue().get();
                     if (dict != null) {
-                        dict.clearAndFlushDictionary();
+                        dict.clear();
                     }
                 }
             }
diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
index 504e9b2..8a29c35 100644
--- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java
@@ -18,7 +18,9 @@
 
 import android.content.Context;
 
+import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.Dictionary;
+import com.android.inputmethod.latin.ExpandableBinaryDictionary;
 
 import java.io.File;
 import java.util.Locale;
@@ -40,13 +42,36 @@
                 dictFile);
     }
 
-    public void cancelAddingUserHistory(final String word0, final String word1) {
-        removeBigramDynamically(word0, word1);
-    }
-
     @Override
     public boolean isValidWord(final String word) {
         // Strings out of this dictionary should not be considered existing words.
         return false;
     }
+
+    /**
+     * Pair will be added to the user history dictionary.
+     *
+     * The first word may be null. That means we don't know the context, in other words,
+     * it's only a unigram. The first word may also be an empty string : this means start
+     * context, as in beginning of a sentence for example.
+     * The second word may not be null (a NullPointerException would be thrown).
+     */
+    public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary,
+            final String word0, final String word1, final boolean isValid, final int timestamp) {
+        if (word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
+                (word0 != null && word0.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
+            return;
+        }
+        final int frequency = isValid ?
+                FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS;
+        userHistoryDictionary.addWordDynamically(word1, frequency, null /* shortcutTarget */,
+                0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp);
+        // Do not insert a word as a bigram of itself
+        if (word1.equals(word0)) {
+            return;
+        }
+        if (null != word0) {
+            userHistoryDictionary.addBigramDynamically(word0, word1, frequency, timestamp);
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
index 60599f6..4399ba0 100644
--- a/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
+++ b/tests/src/com/android/inputmethod/latin/personalization/UserHistoryDictionaryTests.java
@@ -111,7 +111,7 @@
     private static void addToDict(final UserHistoryDictionary dict, final List<String> words) {
         String prevWord = null;
         for (String word : words) {
-            dict.addToDictionary(prevWord, word, true,
+            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true,
                     (int)TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
             prevWord = word;
         }
@@ -147,7 +147,7 @@
         final UserHistoryDictionary dict = PersonalizationHelper.getUserHistoryDictionary(
                 mContext, locale);
         dict.waitAllTasksForTests();
-        dict.clearAndFlushDictionary();
+        dict.clear();
         dict.close();
         dict.waitAllTasksForTests();
     }
@@ -262,7 +262,7 @@
         dict.waitAllTasksForTests();
         String prevWord = null;
         for (final String word : words) {
-            dict.addToDictionary(prevWord, word, true, mCurrentTime);
+            UserHistoryDictionary.addToDictionary(dict, prevWord, word, true, mCurrentTime);
             prevWord = word;
             assertTrue(dict.isInUnderlyingBinaryDictionaryForTests(word));
         }