Merge "Move variables for I/O of a jni method to traverse session."
diff --git a/dictionaries/es_wordlist.combined.gz b/dictionaries/es_wordlist.combined.gz
index 181a958..c0a5264 100644
--- a/dictionaries/es_wordlist.combined.gz
+++ b/dictionaries/es_wordlist.combined.gz
Binary files differ
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 6f4e602..5d8ef43 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -86,7 +86,7 @@
         </receiver>
 
         <activity android:name=".settings.SettingsActivity"
-                android:theme="@style/platformActivityTheme"
+                android:theme="@style/platformSettingsTheme"
                 android:label="@string/english_ime_settings"
                 android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
@@ -95,7 +95,7 @@
         </activity>
 
         <activity android:name=".spellcheck.SpellCheckerSettingsActivity"
-                  android:theme="@style/platformActivityTheme"
+                  android:theme="@style/platformSettingsTheme"
                   android:label="@string/android_spell_checker_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -103,7 +103,7 @@
         </activity>
 
         <activity android:name=".settings.DebugSettingsActivity"
-                android:theme="@style/platformActivityTheme"
+                android:theme="@style/platformSettingsTheme"
                 android:label="@string/english_ime_debug_settings">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -150,7 +150,7 @@
         </receiver>
 
         <activity android:name="com.android.inputmethod.dictionarypack.DictionarySettingsActivity"
-                android:theme="@style/platformActivityTheme"
+                android:theme="@style/platformSettingsTheme"
                 android:label="@string/dictionary_settings_title"
                 android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
diff --git a/java/res/layout/dictionary_line.xml b/java/res/layout/dictionary_line.xml
index 7268cd4..bb1843d 100644
--- a/java/res/layout/dictionary_line.xml
+++ b/java/res/layout/dictionary_line.xml
@@ -42,7 +42,7 @@
       android:orientation="vertical">
 
     <TextView
-        android:id="@+android:id/title"
+        android:id="@android:id/title"
         android:layout_marginLeft="5dip"
         android:layout_marginStart="5dip"
         android:layout_width="wrap_content"
@@ -59,7 +59,7 @@
         android:layout_marginLeft="5dip">
 
       <TextView
-          android:id="@+android:id/summary"
+          android:id="@android:id/summary"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:textAppearance="?android:attr/textAppearanceSmall"
@@ -77,14 +77,14 @@
   </LinearLayout>
 
   <com.android.inputmethod.dictionarypack.ButtonSwitcher
-      android:id="@+android:id/wordlist_button_switcher"
+      android:id="@+id/wordlist_button_switcher"
       android:layout_weight="0"
       android:layout_marginStart="13dip"
       android:layout_marginLeft="13dip"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
     <Button
-        android:id="@+android:id/dict_install_button"
+        android:id="@+id/dict_install_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="right|center_vertical"
@@ -92,7 +92,7 @@
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:text="@string/install_dict" />
     <Button
-        android:id="@+android:id/dict_cancel_button"
+        android:id="@+id/dict_cancel_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="right|center_vertical"
@@ -100,7 +100,7 @@
         android:textAppearance="?android:attr/textAppearanceMedium"
         android:text="@string/cancel_download_dict" />
     <Button
-        android:id="@+android:id/dict_delete_button"
+        android:id="@+id/dict_delete_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="right|center_vertical"
diff --git a/java/res/layout/key_preview.xml b/java/res/layout/key_preview.xml
deleted file mode 100644
index 16d4c72..0000000
--- a/java/res/layout/key_preview.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 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.
-*/
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:minWidth="32dp"
-    android:gravity="center"
-    style="?attr/keyPreviewTextViewStyle"
-/>
diff --git a/java/res/raw/main_es.dict b/java/res/raw/main_es.dict
index 83eefe4..0911b70 100644
--- a/java/res/raw/main_es.dict
+++ b/java/res/raw/main_es.dict
Binary files differ
diff --git a/java/res/values-km-rKH/strings-emoji-descriptions.xml b/java/res/values-km-rKH/strings-emoji-descriptions.xml
index 757df50..9f1d997 100644
--- a/java/res/values-km-rKH/strings-emoji-descriptions.xml
+++ b/java/res/values-km-rKH/strings-emoji-descriptions.xml
@@ -25,16 +25,16 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="spoken_emoji_00A9" msgid="2859822817116803638">"សញ្ញា​រក្សា​សិទ្ធ​"</string>
+    <string name="spoken_emoji_00A9" msgid="2859822817116803638">"សញ្ញា​រក្សា​សិទ្ធ"</string>
     <string name="spoken_emoji_00AE" msgid="7708335454134589027">"សញ្ញា​​​ចុះ​បញ្ជី"</string>
     <string name="spoken_emoji_203C" msgid="153340916701508663">"សញ្ញា​ឧទាន​​ពីរ"</string>
-    <string name="spoken_emoji_2049" msgid="4877256448299555371">"សញ្ញា​​ឧទាន​សញ្ញា​សួរ​​"</string>
+    <string name="spoken_emoji_2049" msgid="4877256448299555371">"សញ្ញា​​ឧទាន​សញ្ញា​សួរ"</string>
     <string name="spoken_emoji_2122" msgid="9188440722954720429">"សញ្ញា​​​និក្ខិត្តសញ្ញា"</string>
     <string name="spoken_emoji_2139" msgid="9114342638917304327">"ប្រភព​ព័ត៌មាន"</string>
     <string name="spoken_emoji_2194" msgid="8055202727034946680">"ព្រួញ​ឆ្វេងស្ដាំ"</string>
     <string name="spoken_emoji_2195" msgid="8028122253301087407">"ព្រួញ​ឡើង​លើ​ចុះក្រោម"</string>
     <string name="spoken_emoji_2196" msgid="4019164898967854363">"ព្រួញ​ទិសពាយព្យ"</string>
-    <string name="spoken_emoji_2197" msgid="4255723717709017801">"ព្រួញ​​ទិស​ឥសាន្ត​ឦសាន្ត​"</string>
+    <string name="spoken_emoji_2197" msgid="4255723717709017801">"ព្រួញ​​ទិស​ឥសាន្ត​ឦសាន្ត"</string>
     <string name="spoken_emoji_2198" msgid="1452063451313622090">"ព្រួញ​​ទិស​អាគ្នេយ៍"</string>
     <string name="spoken_emoji_2199" msgid="6942722693368807849">"ព្រួញ​​ទិស​និរតី"</string>
     <string name="spoken_emoji_21A9" msgid="5204750172335111188">"ព្រួញ​ទៅ​ឆ្វេង​មាន​ទំពក់"</string>
@@ -45,7 +45,7 @@
     <string name="spoken_emoji_23EA" msgid="2251396938087774944">"ត្រីកោណ​ខ្មៅ​ពីរ​ចង្អុល​ទៅ​ឆ្វេង"</string>
     <string name="spoken_emoji_23EB" msgid="3746885195641491865">"ត្រីកោណ​ខ្មៅ​ពីរ​ចង្អុល​​ឡើង​លើ"</string>
     <string name="spoken_emoji_23EC" msgid="7852372752901163416">"ត្រីកោណ​ខ្មៅ​ពីរ​ចង្អុល​​ចុះក្រោម"</string>
-    <string name="spoken_emoji_23F0" msgid="8474219588750627870">"នាឡិកា​រោទ៍​"</string>
+    <string name="spoken_emoji_23F0" msgid="8474219588750627870">"នាឡិកា​រោទ៍"</string>
     <string name="spoken_emoji_23F3" msgid="166900119581024371">"កែវ​ពិសោធន៍​មាន​ខ្សាច់ហូរ"</string>
     <string name="spoken_emoji_24C2" msgid="3948348737566038470">"អក្សរ​អឹម​ធំ​ក្នុង​រង្វង់"</string>
     <string name="spoken_emoji_25AA" msgid="7865181015100227349">"ការ៉េ​តូច​​ពណ៌ខ្មៅ"</string>
@@ -195,7 +195,7 @@
     <string name="spoken_emoji_1F312" msgid="4458575672576125401">"ព្រះ​ចន្ទ​មួយ​ចំណិត​ស្ដាំ"</string>
     <string name="spoken_emoji_1F313" msgid="7599181787989497294">"ព្រះ​ចន្ទ​ពាក់​កណ្ដាល"</string>
     <string name="spoken_emoji_1F314" msgid="4898293184964365413">"ព្រះ​ចន្ទ​មួយ​ចំណិត​ឆ្វេង"</string>
-    <string name="spoken_emoji_1F315" msgid="3218117051779496309">"ព្រះចន្ទ​ពេញ​វង់​"</string>
+    <string name="spoken_emoji_1F315" msgid="3218117051779496309">"ព្រះចន្ទ​ពេញ​វង់"</string>
     <string name="spoken_emoji_1F316" msgid="2061317145777689569">"ព្រះ​ចន្ទ​ភ្លឺ​មួយ​ចំហៀង"</string>
     <string name="spoken_emoji_1F317" msgid="2721090687319539049">"ព្រះ​ចន្ទ​ភ្លឺ​ពាក់​កណ្ដាល"</string>
     <string name="spoken_emoji_1F318" msgid="3814091755648887570">"ព្រះ​ចន្ទ​ភ្លឺ​មួយ​ចំណិត​ឆ្វេង"</string>
@@ -262,7 +262,7 @@
     <string name="spoken_emoji_1F365" msgid="4963815540953316307">"នំ​ត្រី​រាង​មូល"</string>
     <string name="spoken_emoji_1F366" msgid="7862401745277049404">"ការ៉េម​​បំពង់"</string>
     <string name="spoken_emoji_1F367" msgid="7447972978281980414">"ការ៉េម​កែវ"</string>
-    <string name="spoken_emoji_1F368" msgid="7790003146142724913">"ការ៉េម​"</string>
+    <string name="spoken_emoji_1F368" msgid="7790003146142724913">"ការ៉េម"</string>
     <string name="spoken_emoji_1F369" msgid="7383712944084857350">"ដូណាត់"</string>
     <string name="spoken_emoji_1F36A" msgid="2726271795913042295">"ខូគី"</string>
     <string name="spoken_emoji_1F36B" msgid="6342163604299875931">"​​សូកូឡា"</string>
@@ -280,7 +280,7 @@
     <string name="spoken_emoji_1F377" msgid="1762398562314172075">"កែវ​ស្រា"</string>
     <string name="spoken_emoji_1F378" msgid="5528234560590117516">"កែវ​ស្រា​ក្រឡុក"</string>
     <string name="spoken_emoji_1F379" msgid="790581290787943325">"ភេសជ្ជៈ​​​ត្រូពិក"</string>
-    <string name="spoken_emoji_1F37A" msgid="391966822450619516">"កែវ​​ស្រាបៀ​"</string>
+    <string name="spoken_emoji_1F37A" msgid="391966822450619516">"កែវ​​ស្រាបៀ"</string>
     <string name="spoken_emoji_1F37B" msgid="9015043286465670662">"ជល់​កែវ​ស្រាបៀ"</string>
     <string name="spoken_emoji_1F37C" msgid="2532113819464508894">"ដប​ទឹកដោះ​គោ"</string>
     <string name="spoken_emoji_1F380" msgid="3487363857092458827">"ខ្សែ​បូ"</string>
@@ -313,7 +313,7 @@
     <string name="spoken_emoji_1F3A7" msgid="837856608794094105">"កាស"</string>
     <string name="spoken_emoji_1F3A8" msgid="2332260356509244587">"ក្ដារ​លាយ​ពណ៌​វិចិត្រករ"</string>
     <string name="spoken_emoji_1F3A9" msgid="9045869366525115256">"មួក​​​សម្ដែង​សិល្បៈ"</string>
-    <string name="spoken_emoji_1F3AA" msgid="5728760354237132">"តង់​សៀក​"</string>
+    <string name="spoken_emoji_1F3AA" msgid="5728760354237132">"តង់​សៀក"</string>
     <string name="spoken_emoji_1F3AB" msgid="1657997517193216284">"សំបុត្រ"</string>
     <string name="spoken_emoji_1F3AC" msgid="4317366554314492152">"បន្ទះកណ្ដឹង"</string>
     <string name="spoken_emoji_1F3AD" msgid="607157286336130470">"សម្ដែង​សិល្បៈ"</string>
@@ -334,10 +334,10 @@
     <string name="spoken_emoji_1F3BC" msgid="1608424748821446230">"និមិត្តសញ្ញា​តន្ត្រី"</string>
     <string name="spoken_emoji_1F3BD" msgid="5490786111375627777">"អាវ​កីឡា​​មាន​ខ្សែ​ឆៀង"</string>
     <string name="spoken_emoji_1F3BE" msgid="1851613105691627931">"រ៉ាកែត និង​បាល់"</string>
-    <string name="spoken_emoji_1F3BF" msgid="6862405997423247921">"ជិះ​ស្គី ​និង​ក្ដារ​​ស្គី​"</string>
+    <string name="spoken_emoji_1F3BF" msgid="6862405997423247921">"ជិះ​ស្គី ​និង​ក្ដារ​​ស្គី"</string>
     <string name="spoken_emoji_1F3C0" msgid="7421420756115104085">"បាល់​បោះ​ និង​វណ្ណ​មូល"</string>
     <string name="spoken_emoji_1F3C1" msgid="6926537251677319922">"ទង់ជាតិ​ប្រណាំង​ម៉ូតូ"</string>
-    <string name="spoken_emoji_1F3C2" msgid="5708596929237987082">"អ្នក​ជិះ​​ក្ដារ​រំអិល​លើ​ព្រិល​"</string>
+    <string name="spoken_emoji_1F3C2" msgid="5708596929237987082">"អ្នក​ជិះ​​ក្ដារ​រំអិល​លើ​ព្រិល"</string>
     <string name="spoken_emoji_1F3C3" msgid="5850982999510115824">"អ្នក​រត់"</string>
     <string name="spoken_emoji_1F3C4" msgid="8468355585994639838">"អ្នក​ជិះ​ទូក​រអិល​លើ​ទឹក"</string>
     <string name="spoken_emoji_1F3C6" msgid="9094474706847545409">"ពាន​រង្វាន់"</string>
@@ -354,7 +354,7 @@
     <string name="spoken_emoji_1F3E6" msgid="342132788513806214">"ធនាគារ"</string>
     <string name="spoken_emoji_1F3E7" msgid="6322352038284944265">"ម៉ាស៊ីន​​អេធីអឹម"</string>
     <string name="spoken_emoji_1F3E8" msgid="5864918444350599907">"សណ្ឋាគារ"</string>
-    <string name="spoken_emoji_1F3E9" msgid="7830416185375326938">"សណ្ឋាគារ​ក្ដី​ស្រឡាញ់​"</string>
+    <string name="spoken_emoji_1F3E9" msgid="7830416185375326938">"សណ្ឋាគារ​ក្ដី​ស្រឡាញ់"</string>
     <string name="spoken_emoji_1F3EA" msgid="5081084413084360479">"ហាង​​ទំនិញ ២៤​ម៉ោង"</string>
     <string name="spoken_emoji_1F3EB" msgid="7010966528205150525">"សាលារៀន"</string>
     <string name="spoken_emoji_1F3EC" msgid="4845978861878295154">"ហាង​​ទំនិញធំៗ"</string>
@@ -439,12 +439,12 @@
     <string name="spoken_emoji_1F44D" msgid="6182553970602667815">"​មេដៃ​ឡើង​លើ"</string>
     <string name="spoken_emoji_1F44E" msgid="8030851867365111809">"​មេដៃ​ចុះ​ក្រោម"</string>
     <string name="spoken_emoji_1F44F" msgid="5148753662268213389">"ទះ​ដៃ"</string>
-    <string name="spoken_emoji_1F450" msgid="1012021072085157054">"លា​ដៃ​"</string>
+    <string name="spoken_emoji_1F450" msgid="1012021072085157054">"លា​ដៃ"</string>
     <string name="spoken_emoji_1F451" msgid="8257466714629051320">"មកុដ"</string>
     <string name="spoken_emoji_1F452" msgid="4567394011149905466">"មួក​​ស្ត្រី"</string>
     <string name="spoken_emoji_1F453" msgid="5978410551173163010">"វ៉ែនតា"</string>
-    <string name="spoken_emoji_1F454" msgid="348469036193323252">"ក្រ​វ៉ាត់​ករ​"</string>
-    <string name="spoken_emoji_1F455" msgid="5665118831861433578">"អាវ​យឺត​​"</string>
+    <string name="spoken_emoji_1F454" msgid="348469036193323252">"ក្រ​វ៉ាត់​ករ"</string>
+    <string name="spoken_emoji_1F455" msgid="5665118831861433578">"អាវ​យឺត"</string>
     <string name="spoken_emoji_1F456" msgid="1890991330923356408">"ខោ​ខោវប៊យ"</string>
     <string name="spoken_emoji_1F457" msgid="3904310482655702620">"សំលៀក​បំពាក់"</string>
     <string name="spoken_emoji_1F458" msgid="5704243858031107692">"គី​ម៉ូណូ"</string>
@@ -463,8 +463,8 @@
     <string name="spoken_emoji_1F465" msgid="4461307702499679879">"គណនី"</string>
     <string name="spoken_emoji_1F466" msgid="1938873085514108889">"ក្មេង​​ប្រុស"</string>
     <string name="spoken_emoji_1F467" msgid="8237080594860144998">"ក្មេង​ស្រី"</string>
-    <string name="spoken_emoji_1F468" msgid="6081300722526675382">"បុរស​"</string>
-    <string name="spoken_emoji_1F469" msgid="1090140923076108158">"ស្ត្រី​"</string>
+    <string name="spoken_emoji_1F468" msgid="6081300722526675382">"បុរស"</string>
+    <string name="spoken_emoji_1F469" msgid="1090140923076108158">"ស្ត្រី"</string>
     <string name="spoken_emoji_1F46A" msgid="5063570981942606595">"គ្រួសារ"</string>
     <string name="spoken_emoji_1F46B" msgid="6795882374287327952">"បុរស​​ និង​ស្ត្រី​កាន់ដៃ​គ្នា"</string>
     <string name="spoken_emoji_1F46C" msgid="6844464165783964495">"បុរស​ពីរ​នាក់​កាន់​ដៃ​គ្នា"</string>
@@ -490,16 +490,16 @@
     <string name="spoken_emoji_1F480" msgid="3696253485164878739">"លលាដ៍​ក្បាល"</string>
     <string name="spoken_emoji_1F481" msgid="320408708521966893">"អ្នក​ផ្ដល់​ព័ត៌មាន"</string>
     <string name="spoken_emoji_1F482" msgid="3424354860245608949">"អ្នក​យាម"</string>
-    <string name="spoken_emoji_1F483" msgid="3221113594843849083">"អ្នក​រាំ​"</string>
+    <string name="spoken_emoji_1F483" msgid="3221113594843849083">"អ្នក​រាំ"</string>
     <string name="spoken_emoji_1F484" msgid="7348014979080444885">"ក្រេម​លាប​បបូរ​មាត់"</string>
     <string name="spoken_emoji_1F485" msgid="6133507975565116339">"ថ្នាំ​លាប​​​ក្រចក"</string>
     <string name="spoken_emoji_1F486" msgid="9085459968247394155">"ម៉ាស្សា​មុខ"</string>
     <string name="spoken_emoji_1F487" msgid="1479113637259592150">"កាត់សក់"</string>
     <string name="spoken_emoji_1F488" msgid="6922559285234100252">"ស្លាក​សញ្ញា​កាត់សក់"</string>
     <string name="spoken_emoji_1F489" msgid="8114863680950147305">"ស៊ីរ៉ាំង"</string>
-    <string name="spoken_emoji_1F48A" msgid="8526843630145963032">"ថ្នាំ​គ្រាប់​"</string>
+    <string name="spoken_emoji_1F48A" msgid="8526843630145963032">"ថ្នាំ​គ្រាប់"</string>
     <string name="spoken_emoji_1F48B" msgid="2538528967897640292">"ស្នាម​ថើប"</string>
-    <string name="spoken_emoji_1F48C" msgid="1681173271652890232">"លិខិត​ស្នេហា​"</string>
+    <string name="spoken_emoji_1F48C" msgid="1681173271652890232">"លិខិត​ស្នេហា"</string>
     <string name="spoken_emoji_1F48D" msgid="8259886164999042373">"រោទ៍"</string>
     <string name="spoken_emoji_1F48E" msgid="8777981696011111101">"ត្បូង​ថ្ម"</string>
     <string name="spoken_emoji_1F48F" msgid="741593675183677907">"ថើប"</string>
@@ -525,7 +525,7 @@
     <string name="spoken_emoji_1F4A3" msgid="6378351742957821735">"គ្រាប់បែក"</string>
     <string name="spoken_emoji_1F4A4" msgid="7217736258870346625">"និមិត្ត​សញ្ញា​​ដេក"</string>
     <string name="spoken_emoji_1F4A5" msgid="5401995723541239858">"និមិត្ត​សញ្ញា​​ប៉ះ​ទង្គិច​គ្នា"</string>
-    <string name="spoken_emoji_1F4A6" msgid="3837802182716483848">"និមិត្ត​សញ្ញា​​ស្រក់​ញើស​"</string>
+    <string name="spoken_emoji_1F4A6" msgid="3837802182716483848">"និមិត្ត​សញ្ញា​​ស្រក់​ញើស"</string>
     <string name="spoken_emoji_1F4A7" msgid="5718438987757885141">"ដំណក់​ទឹក"</string>
     <string name="spoken_emoji_1F4A8" msgid="4472108229720006377">"និមិត្ត​សញ្ញា​​ដកឃ្លា"</string>
     <string name="spoken_emoji_1F4A9" msgid="1240958472788430032">"គំនរ​ធូលី"</string>
@@ -539,7 +539,7 @@
     <string name="spoken_emoji_1F4B1" msgid="8339494003418572905">"ប្ដូរ​​រូបិយប័ណ្ណ"</string>
     <string name="spoken_emoji_1F4B2" msgid="3179159430187243132">"សញ្ញា​ដុល្លារ"</string>
     <string name="spoken_emoji_1F4B3" msgid="5375412518221759596">"កាត​​ឥណទាន"</string>
-    <string name="spoken_emoji_1F4B4" msgid="1068592463669453204">"ក្រដាស​ប្រាក់​​ធនាគារ​មាន​សញ្ញា​​យ៉េន​​"</string>
+    <string name="spoken_emoji_1F4B4" msgid="1068592463669453204">"ក្រដាស​ប្រាក់​​ធនាគារ​មាន​សញ្ញា​​យ៉េន"</string>
     <string name="spoken_emoji_1F4B5" msgid="1426708699891832564">"លុយដុល្លារ"</string>
     <string name="spoken_emoji_1F4B6" msgid="8289249930736444837">"ក្រដាស​ប្រាក់​​ធនាគារ​មាន​សញ្ញា​អឺរ៉ូ"</string>
     <string name="spoken_emoji_1F4B7" msgid="5245100496860739429">"ក្រដាស​ប្រាក់​​ធនាគារ​មាន​សញ្ញា​​​ផោន"</string>
@@ -547,7 +547,7 @@
     <string name="spoken_emoji_1F4B9" msgid="647509393536679903">"ក្រាហ្វិក​និន្នាការ​ឡើង​​មាន​​សញ្ញា​យ៉េន"</string>
     <string name="spoken_emoji_1F4BA" msgid="1269737854891046321">"កៅអី"</string>
     <string name="spoken_emoji_1F4BB" msgid="6252883563347816451">"កុំព្យូទ័រ​ផ្ទាល់ខ្លួន"</string>
-    <string name="spoken_emoji_1F4BC" msgid="6182597732218446206">"វ៉ា​លី​"</string>
+    <string name="spoken_emoji_1F4BC" msgid="6182597732218446206">"វ៉ា​លី"</string>
     <string name="spoken_emoji_1F4BD" msgid="5820961044768829176">"ឌីស​​តូច"</string>
     <string name="spoken_emoji_1F4BE" msgid="4754542485835379808">"ថា​ស​​ទន់"</string>
     <string name="spoken_emoji_1F4BF" msgid="2237481756984721795">"ថាស"</string>
@@ -557,7 +557,7 @@
     <string name="spoken_emoji_1F4C3" msgid="3727274466173970142">"ទំព័រ​​កោង"</string>
     <string name="spoken_emoji_1F4C4" msgid="4382570710795501612">"ទំព័រ​បញ្ឈរ"</string>
     <string name="spoken_emoji_1F4C5" msgid="8693944622627762487">"ប្រតិទិន"</string>
-    <string name="spoken_emoji_1F4C6" msgid="8469908708708424640">"ហែក​ប្រតិទិន​"</string>
+    <string name="spoken_emoji_1F4C6" msgid="8469908708708424640">"ហែក​ប្រតិទិន"</string>
     <string name="spoken_emoji_1F4C7" msgid="2665313547987324495">"​កាត​រៀប​តាម​អក្សរ"</string>
     <string name="spoken_emoji_1F4C8" msgid="8007686702282833600">"ក្រាហ្វិក​មាន​និន្នាការ​ឡើង"</string>
     <string name="spoken_emoji_1F4C9" msgid="2271951411192893684">"ក្រាហ្វិក​មាន​និន្នាការ​ចុះ"</string>
@@ -573,11 +573,11 @@
     <string name="spoken_emoji_1F4D3" msgid="5873386492793610808">"សៀវភៅ"</string>
     <string name="spoken_emoji_1F4D4" msgid="4754469936418776360">"សៀវភៅ​មាន​ក្រប​ពណ៌"</string>
     <string name="spoken_emoji_1F4D5" msgid="4642713351802778905">"សៀវភៅ​​បិទ"</string>
-    <string name="spoken_emoji_1F4D6" msgid="6987347918381807186">"សៀវភៅ​បើក​​"</string>
+    <string name="spoken_emoji_1F4D6" msgid="6987347918381807186">"សៀវភៅ​បើក"</string>
     <string name="spoken_emoji_1F4D7" msgid="7813394163241379223">"សៀវភៅ​​ពណ៌​បៃតង"</string>
     <string name="spoken_emoji_1F4D8" msgid="7189799718984979521">"សៀវភៅ​​ពណ៌​ខៀវ"</string>
     <string name="spoken_emoji_1F4D9" msgid="3874664073186440225">"សៀវភៅ​​ពណ៌​ទឹកក្រូច"</string>
-    <string name="spoken_emoji_1F4DA" msgid="872212072924287762">"សៀវភៅ​"</string>
+    <string name="spoken_emoji_1F4DA" msgid="872212072924287762">"សៀវភៅ"</string>
     <string name="spoken_emoji_1F4DB" msgid="2015183603583392969">"ស្លាកឈ្មោះ"</string>
     <string name="spoken_emoji_1F4DC" msgid="5075845110932456783">"ក្រដាស​រមូរ"</string>
     <string name="spoken_emoji_1F4DD" msgid="2494006707147586786">"កំណត់ចំណាំ"</string>
@@ -589,7 +589,7 @@
     <string name="spoken_emoji_1F4E3" msgid="5588916572878599224">"ឧបករណ៍​បំពង​សំឡេង"</string>
     <string name="spoken_emoji_1F4E4" msgid="2063561529097749707">"ថា​ស​​​ចេញ"</string>
     <string name="spoken_emoji_1F4E5" msgid="3232462702926143576">"ថា​ស​ចូល"</string>
-    <string name="spoken_emoji_1F4E6" msgid="3399454337197561635">"កញ្ចប់​"</string>
+    <string name="spoken_emoji_1F4E6" msgid="3399454337197561635">"កញ្ចប់"</string>
     <string name="spoken_emoji_1F4E7" msgid="5557136988503873238">"និមិត្ត​សញ្ញា​អ៊ីមែល"</string>
     <string name="spoken_emoji_1F4E8" msgid="30698793974124123">"ស្រោម​​សំបុត្រ​ចូល"</string>
     <string name="spoken_emoji_1F4E9" msgid="5947550337678643166">"ស្រោម​សំបុត្រ​​មាន​សញ្ញា​ព្រួញ​ពី​លើ"</string>
@@ -626,7 +626,7 @@
     <string name="spoken_emoji_1F50C" msgid="7793219132036431680">"ដុយ​អគ្គិសនី"</string>
     <string name="spoken_emoji_1F50D" msgid="8140244710637926780">"កែវ​ពង្រីក​ចង្អុល​ខាង​ឆ្វេង"</string>
     <string name="spoken_emoji_1F50E" msgid="4751821352839693365">"កែវ​ពង្រីក​ចង្អុល​ខាង​ស្ដាំ"</string>
-    <string name="spoken_emoji_1F50F" msgid="915079280472199605">"ចាក់សោ​​​ដោយ​ប្រើ​​ប៊ិច​"</string>
+    <string name="spoken_emoji_1F50F" msgid="915079280472199605">"ចាក់សោ​​​ដោយ​ប្រើ​​ប៊ិច"</string>
     <string name="spoken_emoji_1F510" msgid="7658381761691758318">"បិទ​​សោ​ដោយ​ប្រើ​​​​កូនសោ"</string>
     <string name="spoken_emoji_1F511" msgid="262319867774655688">"សោ"</string>
     <string name="spoken_emoji_1F512" msgid="5628688337255115175">"ចាក់សោ"</string>
@@ -645,15 +645,15 @@
     <string name="spoken_emoji_1F51F" msgid="8673370823728653973">"គ្រាប់​ចុច​ ១០"</string>
     <string name="spoken_emoji_1F520" msgid="7335109890337048900">"និមិត្ត​សញ្ញា​បញ្ចូល​សម្រាប់​អក្សរ​ឡាតាំង​ធំ"</string>
     <string name="spoken_emoji_1F521" msgid="2693185864450925778">"និមិត្ត​សញ្ញា​បញ្ចូល​សម្រាប់​អក្សរ​ឡាតាំង​តូច"</string>
-    <string name="spoken_emoji_1F522" msgid="8419130286280673347">"និមិត្ត​សញ្ញា​បញ្ចូល​សម្រាប់​​លេខ​"</string>
+    <string name="spoken_emoji_1F522" msgid="8419130286280673347">"និមិត្ត​សញ្ញា​បញ្ចូល​សម្រាប់​​លេខ"</string>
     <string name="spoken_emoji_1F523" msgid="3318053476401719421">"ការ​បញ្ចូល​និមិត្តសញ្ញា"</string>
     <string name="spoken_emoji_1F524" msgid="1625073997522316331">"និមិត្ត​សញ្ញា​បញ្ចូល​សម្រាប់​អក្សរ​ឡាតាំង"</string>
     <string name="spoken_emoji_1F525" msgid="4083884189172963790">"ភ្លើង"</string>
     <string name="spoken_emoji_1F526" msgid="2035494936742643580">"ពិល​​អគ្គិសនី"</string>
     <string name="spoken_emoji_1F527" msgid="134257142354034271">"ម៉ាឡេត"</string>
     <string name="spoken_emoji_1F528" msgid="700627429570609375">"ញញួរ"</string>
-    <string name="spoken_emoji_1F529" msgid="7480548235904988573">"ឡោ​ស៊ី​​"</string>
-    <string name="spoken_emoji_1F52A" msgid="7613580031502317893">"កាំបិត​​"</string>
+    <string name="spoken_emoji_1F529" msgid="7480548235904988573">"ឡោ​ស៊ី"</string>
+    <string name="spoken_emoji_1F52A" msgid="7613580031502317893">"កាំបិត"</string>
     <string name="spoken_emoji_1F52B" msgid="4554906608328118613">"កាំភ្លើង​ខ្លី"</string>
     <string name="spoken_emoji_1F52C" msgid="1330294501371770790">"មីក្រូទស្សន៍"</string>
     <string name="spoken_emoji_1F52D" msgid="7549551775445177140">"កែវ​យឹត"</string>
@@ -662,7 +662,7 @@
     <string name="spoken_emoji_1F530" msgid="3572898444281774023">"និមិត្តសញ្ញា​ជប៉ុន​សម្រាប់​អ្នក​ចាប់ផ្ដើម"</string>
     <string name="spoken_emoji_1F531" msgid="5225633376450025396">"លំពែង​មុខ​បី"</string>
     <string name="spoken_emoji_1F532" msgid="9169568490485180779">"ប៊ូតុង​ការេ​ពណ៌​ខ្មៅ"</string>
-    <string name="spoken_emoji_1F533" msgid="6554193837201918598">"ប៊ូតុង​ការ៉េ​ពណ៌​ស​"</string>
+    <string name="spoken_emoji_1F533" msgid="6554193837201918598">"ប៊ូតុង​ការ៉េ​ពណ៌​ស"</string>
     <string name="spoken_emoji_1F534" msgid="8339298801331865340">"រង្វង់​ពណ៌​ក្រហម​​​ធំ"</string>
     <string name="spoken_emoji_1F535" msgid="1227403104835533512">"រង្វង់​ពណ៌​ខៀវ​ធំ"</string>
     <string name="spoken_emoji_1F536" msgid="5477372445510469331">"ពេជ្រ​ពណ៌​ទឹកក្រូច​ធំ"</string>
@@ -745,8 +745,8 @@
     <string name="spoken_emoji_1F628" msgid="8875777401624904224">"មុខ​ភ័យ​ខ្លាច"</string>
     <string name="spoken_emoji_1F629" msgid="1411538490319190118">"មុខ​​នឿយហត់"</string>
     <string name="spoken_emoji_1F62A" msgid="4726686726690289969">"មុខ​​ងងុយ​គេង"</string>
-    <string name="spoken_emoji_1F62B" msgid="3221980473921623613">"មុខ​អស់កម្លាំង​​"</string>
-    <string name="spoken_emoji_1F62C" msgid="4616356691941225182">"មុខ​ក្រញេវក្រញូវ​"</string>
+    <string name="spoken_emoji_1F62B" msgid="3221980473921623613">"មុខ​អស់កម្លាំង"</string>
+    <string name="spoken_emoji_1F62C" msgid="4616356691941225182">"មុខ​ក្រញេវក្រញូវ"</string>
     <string name="spoken_emoji_1F62D" msgid="4283677508698812232">"មុខ​យំ​លឺៗ"</string>
     <string name="spoken_emoji_1F62E" msgid="726083405284353894">"មុខ​បើក​មាត់"</string>
     <string name="spoken_emoji_1F62F" msgid="7746620088234710962">"មុខ​ស្ងៀមស្ងាត់"</string>
@@ -784,7 +784,7 @@
     <string name="spoken_emoji_1F683" msgid="8772750354339223092">"ទូរ​​រថភ្លើង"</string>
     <string name="spoken_emoji_1F684" msgid="346396777356203608">"រថភ្លើង​ល្បឿន​លឿន"</string>
     <string name="spoken_emoji_1F685" msgid="1237059817190832730">"រថភ្លើង​ល្បឿន​លឿន​​មាន​ច្រមុះ"</string>
-    <string name="spoken_emoji_1F686" msgid="3525197227223620343">"រថភ្លើង​"</string>
+    <string name="spoken_emoji_1F686" msgid="3525197227223620343">"រថភ្លើង"</string>
     <string name="spoken_emoji_1F687" msgid="5110143437960392837">"មេត្រូ"</string>
     <string name="spoken_emoji_1F688" msgid="4702085029871797965">"រថភ្លើង​ប្រើ​​​ពន្លឺ"</string>
     <string name="spoken_emoji_1F689" msgid="2375851019798817094">"ស្ថានីយ"</string>
@@ -803,7 +803,7 @@
     <string name="spoken_emoji_1F696" msgid="6391604457418285404">"តាក់ស៊ី​ខាង​មុខ"</string>
     <string name="spoken_emoji_1F697" msgid="7978399334396733790">"រថយន្ត"</string>
     <string name="spoken_emoji_1F698" msgid="7006050861129732018">"រថយន្ត​ខាង​មុខ"</string>
-    <string name="spoken_emoji_1F699" msgid="630317052666590607">"រថយន្ត​​​សម្រាប់​កម្សាន្ត​"</string>
+    <string name="spoken_emoji_1F699" msgid="630317052666590607">"រថយន្ត​​​សម្រាប់​កម្សាន្ត"</string>
     <string name="spoken_emoji_1F69A" msgid="4739797891735823577">"រថយន្ត​​ចែក​ចាយ"</string>
     <string name="spoken_emoji_1F69B" msgid="4715997280786620649">"ឡាន​កាមីយ៉ុង"</string>
     <string name="spoken_emoji_1F69C" msgid="5557395610750818161">"ត្រាក់ទ័រ"</string>
@@ -819,7 +819,7 @@
     <string name="spoken_emoji_1F6A6" msgid="485575967773793454">"ភ្លើង​ចរាចរណ៍​បញ្ឈរ"</string>
     <string name="spoken_emoji_1F6A7" msgid="6411048933816976794">"សញ្ញា​​សំណង់"</string>
     <string name="spoken_emoji_1F6A8" msgid="6345717218374788364">"រថយន្ត​​ប៉ូលិស​បើក​សារ៉ែន​វិល"</string>
-    <string name="spoken_emoji_1F6A9" msgid="6586380356807600412">"បង្គោល​ទង់ជាតិ​រាង​ត្រីកោណ​"</string>
+    <string name="spoken_emoji_1F6A9" msgid="6586380356807600412">"បង្គោល​ទង់ជាតិ​រាង​ត្រីកោណ"</string>
     <string name="spoken_emoji_1F6AA" msgid="8954448167261738885">"ទ្វារ"</string>
     <string name="spoken_emoji_1F6AB" msgid="5313946262888343544">"សញ្ញា​ហាម​ចូល"</string>
     <string name="spoken_emoji_1F6AC" msgid="6946858177965948288">"​សញ្ញា​ជក់​បារី"</string>
diff --git a/java/res/values-km-rKH/strings.xml b/java/res/values-km-rKH/strings.xml
index 91ba5b9..2cf51d8 100644
--- a/java/res/values-km-rKH/strings.xml
+++ b/java/res/values-km-rKH/strings.xml
@@ -28,7 +28,7 @@
     <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="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>
@@ -38,7 +38,7 @@
     <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">"បង្ហាញ​​សញ្ញា​មើល​​ឃើញ​ខណៈ​ពេល​ដែល​រុញ​ពី​ឆ្វេង ឬ​​គ្រាប់​ចុច​​និមិត្ត​សញ្ញា"</string>
-    <string name="key_preview_popup_dismiss_delay" msgid="6213164897443068248">"​សោ​លេចឡើង​បោះបង់​ការ​​ពន្យារពេល​"</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>
@@ -50,7 +50,7 @@
     <skip />
     <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" 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>
@@ -61,7 +61,7 @@
     <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="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>
@@ -121,7 +121,7 @@
     <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_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="4782116251651288054">"ពិត​ជា​ដំឡើង​ឯកសារ​នេះ​សម្រាប់ <xliff:g id="LANGUAGE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"មាន​កំហុស"</string>
@@ -148,7 +148,7 @@
     <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_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>
@@ -164,10 +164,10 @@
     <string name="message_updating" msgid="4457761393932375219">"ពិនិត្យមើល​បច្ចុប្បន្នភាព"</string>
     <string name="message_loading" msgid="5638680861387748936">"កំពុង​ផ្ទុក..."</string>
     <string name="main_dict_description" msgid="3072821352793492143">"វចនានុក្រម​ចម្បង"</string>
-    <string name="cancel" msgid="6830980399865683324">"បោះ​បង់​"</string>
+    <string name="cancel" msgid="6830980399865683324">"បោះ​បង់"</string>
     <string name="go_to_settings" msgid="3876892339342569259">"ការ​កំណត់"</string>
     <string name="install_dict" msgid="180852772562189365">"ដំឡើង"</string>
-    <string name="cancel_download_dict" msgid="7843340278507019303">"បោះ​បង់​"</string>
+    <string name="cancel_download_dict" msgid="7843340278507019303">"បោះ​បង់"</string>
     <string name="delete_dict" msgid="756853268088330054">"លុប"</string>
     <string name="should_download_over_metered_prompt" msgid="1583881200688185508">"ភាសា​ដែល​បាន​ជ្រើស​នៅ​លើ​ឧបករណ៍​ចល័ត​មាន​វចនានុក្រម​អាច​ប្រើ​បាន។&lt;br/&gt; យើង​ផ្ដល់​អនុសាសន៍​ឲ្យ &lt;b&gt;ទាញ​យក&lt;/b&gt; វចនានុក្រម​ភាសា <xliff:g id="LANGUAGE_NAME">%1$s</xliff:g> ដើម្បី​បង្កើន​បទពិសោធន៍​វាយ​បញ្ចូល​របស់​អ្នក។&lt;br/&gt; &lt;br/&gt; ការ​ទាញ​យក​អាច​ចំណាយ​ពេល​ប្រហែល​ពីរ​នាទី​នៅ​តាម 3G។ ការ​គិត​ថ្លៃ​អាច​អនុវត្ត​ប្រសិន​បើ​អ្នក​មិន​ប្រើ &lt;b&gt;ផែនការ​ទិន្នន័យ​គ្មាន​ដែន​កំណត់&lt;/b&gt;.&lt;br/&gt; បើ​អ្នក​មិន​ប្រាកដ​​ថា​ផែនការ​ណា​មួយ​ដែល​អ្នក​មាន យើង​ផ្ដល់​អនុសាសន៍​ឲ្យ​​ភ្ជាប់​វ៉ាយហ្វាយ ដើម្បី​ចាប់ផ្ដើម​ទាញ​យក​ដោយ​ស្វ័យ​ប្រវត្តិ។&lt;br/&gt; &lt;br/&gt; ជំនួយ៖ អ្នក​អាច​ទាញ​យក និង​លុប​វចនានុក្រម​ដោយ​ចូល​ទៅ​ &lt;b&gt;ភាសា &amp; ការ​បញ្ចូល&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>
@@ -185,7 +185,7 @@
     <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_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>
diff --git a/java/res/values-lo-rLA/strings-emoji-descriptions.xml b/java/res/values-lo-rLA/strings-emoji-descriptions.xml
index 0747fa6..83a702e 100644
--- a/java/res/values-lo-rLA/strings-emoji-descriptions.xml
+++ b/java/res/values-lo-rLA/strings-emoji-descriptions.xml
@@ -210,7 +210,7 @@
     <string name="spoken_emoji_1F330" msgid="3115760035618051575">"​ລູກ​ເກົາ​ລັດ"</string>
     <string name="spoken_emoji_1F331" msgid="5658888205290008691">"​ກ້າ​ໄມ້"</string>
     <string name="spoken_emoji_1F332" msgid="2935650450421165938">"ຕົ້ນ​ໄມ້​ບໍ່​ຜັດ​ໃບ"</string>
-    <string name="spoken_emoji_1F333" msgid="5898847427062482675">"​ຕົ້ນ​ໄມ້​ຜັດໃບ​"</string>
+    <string name="spoken_emoji_1F333" msgid="5898847427062482675">"​ຕົ້ນ​ໄມ້​ຜັດໃບ"</string>
     <string name="spoken_emoji_1F334" msgid="6183375224678417894">"​ຕົ້ນ​ປາມ"</string>
     <string name="spoken_emoji_1F335" msgid="5352418412103584941">"​ກະ​ບອງ​ເພັດ"</string>
     <string name="spoken_emoji_1F337" msgid="3839107352363566289">"​ທິວ​ລິບ"</string>
@@ -450,7 +450,7 @@
     <string name="spoken_emoji_1F458" msgid="5704243858031107692">"​ກິ​ໂມ​ໂນ"</string>
     <string name="spoken_emoji_1F459" msgid="3553148747050035251">"​ບິ​ກີ​ນີ"</string>
     <string name="spoken_emoji_1F45A" msgid="1389654639484716101">"​ເສື້ອ​ຜ້າ​ຜູ່​ຍິງ"</string>
-    <string name="spoken_emoji_1F45B" msgid="1113293170254222904">"ກະ​ເປົາ​"</string>
+    <string name="spoken_emoji_1F45B" msgid="1113293170254222904">"ກະ​ເປົາ"</string>
     <string name="spoken_emoji_1F45C" msgid="3410257778598006936">"ກະ​ເປົາ"</string>
     <string name="spoken_emoji_1F45D" msgid="812176504300064819">"​ກະ​ເປົາ"</string>
     <string name="spoken_emoji_1F45E" msgid="2901741399934723562">"​ເກີບ​ຜູ່​ຊາຍ"</string>
diff --git a/java/res/values-v20/platform-theme.xml b/java/res/values-v20/platform-theme.xml
deleted file mode 100644
index 52e7f35..0000000
--- a/java/res/values-v20/platform-theme.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
-*/
--->
-
-<!-- TODO: This file is temporarily placed under values-v20. -->
-<!-- TODO: It might be moved under values-v21. -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-    <style name="platformActivityTheme" parent="@android:style/Theme.Material.Light" />
-    <style name="platformDialogTheme" parent="@android:style/Theme.Material.Light.Dialog" />
-</resources>
diff --git a/java/res/values-v21/platform-theme.xml b/java/res/values-v21/platform-theme.xml
new file mode 100644
index 0000000..a49e364
--- /dev/null
+++ b/java/res/values-v21/platform-theme.xml
@@ -0,0 +1,38 @@
+<?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">
+    <style name="platformActivityTheme" parent="@android:style/Theme.Material.Light" />
+    <!-- From packages/apps/Settings -->
+    <style name="platformSettingsTheme" parent="@android:style/Theme.Material.Settings">
+        <item name="android:actionBarStyle">@android:style/Widget.Material.Light.ActionBar.Solid</item>
+        <item name="android:alertDialogTheme">@style/Theme.AlertDialog</item>
+    </style>
+    <!-- From packages/apps/Settings -->
+    <style name="Theme.AlertDialog" parent="@*android:style/Theme.Material.Light.Dialog.Alert">
+        <!-- Used by the ActionBar: @color/theme_primary -->
+        <item name="android:colorPrimary">#ff263238</item>
+        <!-- Used by the StatusBar: @color/theme_primary_dark -->
+        <item name="android:colorPrimaryDark">#ff21272b</item>
+        <!-- Used by controls, e.g. CheckBox, ProgressBar, etc.: @color/theme_accent -->
+        <item name="android:colorAccent">#ff009688</item>
+    </style>
+    <style name="platformDialogTheme" parent="@android:style/Theme.Material.Light.Dialog" />
+</resources>
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index a1f478b..fcb919d 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -26,8 +26,6 @@
         <attr name="keyboardViewStyle" format="reference" />
         <!-- MainKeyboardView style -->
         <attr name="mainKeyboardViewStyle" format="reference" />
-        <!-- Key preview text view style -->
-        <attr name="keyPreviewTextViewStyle" format="reference"/>
         <!-- EmojiPalettesView style -->
         <attr name="emojiPalettesViewStyle" format="reference" />
         <!-- MoreKeysKeyboard style -->
@@ -106,8 +104,8 @@
         <attr name="longPressShiftLockTimeout" format="integer" />
         <!-- Ignore special key timeout while typing in millisecond. -->
         <attr name="ignoreAltCodeKeyTimeout" format="integer" />
-        <!-- Layout resource for key press feedback.-->
-        <attr name="keyPreviewLayout" format="reference" />
+        <!-- Background resource for key press feedback.-->
+        <attr name="keyPreviewBackground" format="reference" />
         <!-- Vertical offset of the key press feedback from the key. -->
         <attr name="keyPreviewOffset" format="dimension" />
         <!-- Height of the key press feedback popup. -->
diff --git a/java/res/values/keyboard-themes.xml b/java/res/values/keyboard-themes.xml
index a06082c..28a34c3 100644
--- a/java/res/values/keyboard-themes.xml
+++ b/java/res/values/keyboard-themes.xml
@@ -20,13 +20,14 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- For keyboard color scheme option dialog. -->
     <string-array name="keyboard_theme_names" translatable="false">
-        <!-- TODO: Make this item as translatable string resource. -->
-        <item>Material</item>
-        <item>@string/keyboard_color_scheme_white</item>
-        <item>@string/keyboard_color_scheme_blue</item>
+        <!-- TODO: Implement Material Light theme. -->
+        <item>@string/keyboard_theme_material_dark</item>
+        <item>@string/keyboard_theme_holo_white</item>
+        <item>@string/keyboard_theme_holo_blue</item>
     </string-array>
     <!-- An element must be a keyboard theme id of {@link KeyboardTheme#THEME_ID_*}. -->
     <string-array name="keyboard_theme_ids" translatable="false">
+        <!-- TODO: Implement Material Light theme. -->
         <item>3</item>
         <item>2</item>
         <item>0</item>
diff --git a/java/res/values/platform-theme.xml b/java/res/values/platform-theme.xml
index 8e131a2..e688597 100644
--- a/java/res/values/platform-theme.xml
+++ b/java/res/values/platform-theme.xml
@@ -20,5 +20,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
     <style name="platformActivityTheme" parent="@android:style/Theme.Holo" />
+    <style name="platformSettingsTheme" parent="@android:style/Theme.Holo" />
     <style name="platformDialogTheme" parent="@android:style/Theme.Holo.Dialog" />
 </resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 8849115..9ec3f46 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -36,22 +36,18 @@
     <!-- Option to control whether or not to show a popup with a larger font on each key press. -->
     <string name="popup_on_keypress">Popup on keypress</string>
 
-    <!-- Category title for general settings for Android keyboard -->
-    <string name="general_category">General</string>
-
-    <!-- Category title for text prediction -->
-    <string name="correction_category">Text correction</string>
-
-    <!-- Category title for gesture typing -->
-    <string name="gesture_typing_category">Gesture typing</string>
-
-    <!-- Category title for misc options  -->
-    <string name="misc_category">Other options</string>
-
-    <!-- Option name for advanced settings screen [CHAR LIMIT=25] -->
-    <string name="advanced_settings">Advanced settings</string>
-    <!-- Option summary for advanced settings screen [CHAR LIMIT=65 (two lines) or 30 (fits on one line, preferable)] -->
-    <string name="advanced_settings_summary">Options for experts</string>
+    <!-- Settings screen title for input preferences [CHAR LIMIT=25]-->
+    <string name="settings_screen_input">Input preferences</string>
+    <!-- Settings screen title for appearance preferences [CHAR LIMIT=25] -->
+    <string name="settings_screen_appearances">Appearance</string>
+    <!-- Settings screen title for multi lingual options [CHAR_LIMIT=25] -->
+    <string name="settings_screen_multi_lingual">Multi lingual options</string>
+    <!-- Settings screen title for gesture typing preferences [CHAR_LIMIT=25] -->
+    <string name="settings_screen_gesture">Gesture typing preferences</string>
+    <!-- Settings screen title for text correction options [CHAR_LIMIT=25] -->
+    <string name="settings_screen_correction">Text correction</string>
+    <!-- Settings screen title for advanced settings [CHAR LIMIT=25] -->
+    <string name="settings_screen_advanced">Advanced</string>
 
     <!-- Option name for including other IMEs in the language switch list [CHAR LIMIT=30] -->
     <string name="include_other_imes_in_language_switch_list">Switch to other input methods</string>
@@ -295,12 +291,16 @@
     <!-- Description for Emoji keyboard subtype [CHAR LIMIT=25] -->
     <string name="subtype_emoji">Emoji</string>
 
-    <!-- Title of the preference settings for switching keyboard color scheme [CHAR LIMIT=35] -->
-    <string name="keyboard_color_scheme">Color scheme</string>
-    <!-- The keyboard color scheme name, White [CHAR LIMIT=16] -->
-    <string name="keyboard_color_scheme_white">White</string>
-    <!-- The keyboard color scheme name, Blue [CHAR LIMIT=16] -->
-    <string name="keyboard_color_scheme_blue">Blue</string>
+    <!-- Title of the preference settings for switching keyboard theme [CHAR LIMIT=35] -->
+    <string name="keyboard_theme">Keyboard theme</string>
+    <!-- The keyboard theme name, Holo White [CHAR LIMIT=25] -->
+    <string name="keyboard_theme_holo_white">Holo White</string>
+    <!-- The keyboard theme name, Holo Blue [CHAR LIMIT=25] -->
+    <string name="keyboard_theme_holo_blue">Holo Blue</string>
+    <!-- The keyboard theme name, Material Dark [CHAR LIMIT=25] -->
+    <string name="keyboard_theme_material_dark">Material Dark</string>
+    <!-- The keyboard theme name, Material Light [CHAR LIMIT=25] -->
+    <string name="keyboard_theme_material_light">Material Light</string>
 
     <!-- Title of the preference settings for custom input styles (language and keyboard layout pairs) [CHAR LIMIT=35]-->
     <string name="custom_input_styles_title">Custom input styles</string>
diff --git a/java/res/values/themes-common.xml b/java/res/values/themes-common.xml
index 02a93ca..2b2a80a 100644
--- a/java/res/values/themes-common.xml
+++ b/java/res/values/themes-common.xml
@@ -75,7 +75,6 @@
         <item name="keyRepeatInterval">@integer/config_key_repeat_interval</item>
         <item name="longPressShiftLockTimeout">@integer/config_longpress_shift_lock_timeout</item>
         <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
-        <item name="keyPreviewLayout">@layout/key_preview</item>
         <item name="keyPreviewHeight">@dimen/config_key_preview_height</item>
         <!-- TODO: consolidate key preview linger timeout with the key preview animation parameters. -->
         <item name="keyPreviewLingerTimeout">@integer/config_key_preview_linger_timeout</item>
@@ -106,7 +105,6 @@
     <style
         name="MainKeyboardView"
         parent="KeyboardView" />
-    <style name="KeyPreviewTextView" />
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style name="EmojiPalettesView" />
diff --git a/java/res/values/themes-ics.xml b/java/res/values/themes-ics.xml
index 319b4ae..073ae90 100644
--- a/java/res/values/themes-ics.xml
+++ b/java/res/values/themes-ics.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.ICS</item>
         <item name="keyboardViewStyle">@style/KeyboardView.ICS</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.ICS</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.ICS</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.ICS</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.ICS</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.ICS</item>
@@ -66,6 +65,7 @@
         name="MainKeyboardView.ICS"
         parent="KeyboardView.ICS"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_ics</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_ics</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
@@ -75,12 +75,6 @@
         <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
     </style>
-    <style
-        name="KeyPreviewTextView.ICS"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_ics</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/res/values/themes-klp.xml b/java/res/values/themes-klp.xml
index 208723d..f895de5 100644
--- a/java/res/values/themes-klp.xml
+++ b/java/res/values/themes-klp.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.KLP</item>
         <item name="keyboardViewStyle">@style/KeyboardView.KLP</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.KLP</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.KLP</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.KLP</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.KLP</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.KLP</item>
@@ -66,6 +65,7 @@
         name="MainKeyboardView.KLP"
         parent="KeyboardView.KLP"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_klp</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_klp</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_holo</item>
@@ -75,12 +75,6 @@
         <item name="languageOnSpacebarTextShadowRadius">1.0</item>
         <item name="languageOnSpacebarTextShadowColor">@color/spacebar_text_shadow_color_holo</item>
     </style>
-    <style
-        name="KeyPreviewTextView.KLP"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_klp</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/res/values/themes-lxx-dark.xml b/java/res/values/themes-lxx-dark.xml
index e9a295c..1db8f42 100644
--- a/java/res/values/themes-lxx-dark.xml
+++ b/java/res/values/themes-lxx-dark.xml
@@ -23,7 +23,6 @@
         <item name="keyboardStyle">@style/Keyboard.LXX_Dark</item>
         <item name="keyboardViewStyle">@style/KeyboardView.LXX_Dark</item>
         <item name="mainKeyboardViewStyle">@style/MainKeyboardView.LXX_Dark</item>
-        <item name="keyPreviewTextViewStyle">@style/KeyPreviewTextView.LXX_Dark</item>
         <item name="emojiPalettesViewStyle">@style/EmojiPalettesView.LXX_Dark</item>
         <item name="moreKeysKeyboardStyle">@style/MoreKeysKeyboard.LXX_Dark</item>
         <item name="moreKeysKeyboardViewStyle">@style/MoreKeysKeyboardView.LXX_Dark</item>
@@ -67,6 +66,7 @@
         name="MainKeyboardView.LXX_Dark"
         parent="KeyboardView.LXX_Dark"
     >
+        <item name="keyPreviewBackground">@drawable/keyboard_key_feedback_lxx_dark</item>
         <item name="keyPreviewOffset">@dimen/config_key_preview_offset_holo</item>
         <item name="gestureFloatingPreviewTextColor">@color/highlight_color_lxx_dark</item>
         <item name="gestureFloatingPreviewColor">@color/gesture_floating_preview_color_lxx_dark</item>
@@ -76,12 +76,6 @@
         <!-- A negative value to disable text shadow layer. -->
         <item name="languageOnSpacebarTextShadowRadius">-1.0</item>
     </style>
-    <style
-        name="KeyPreviewTextView.LXX_Dark"
-        parent="KeyPreviewTextView"
-    >
-        <item name="android:background">@drawable/keyboard_key_feedback_lxx_dark</item>
-    </style>
     <!-- Though {@link EmojiPalettesView} doesn't extend {@link KeyboardView}, some views inside it,
          for instance delete button, need themed {@link KeyboardView} attributes. -->
     <style
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 0e9c161..cb951d2 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -18,39 +18,117 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
     android:key="english_ime_settings">
-    <PreferenceCategory
-        android:title="@string/general_category"
-        android:key="general_settings">
-        <CheckBoxPreference
+    <PreferenceScreen
+        android:title="@string/settings_screen_input"
+        android:key="screen_input">
+        <SwitchPreference
             android:key="auto_cap"
             android:title="@string/auto_cap"
             android:summary="@string/auto_cap_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="true" />
-        <CheckBoxPreference
+        <SwitchPreference
+            android:key="pref_key_use_double_space_period"
+            android:title="@string/use_double_space_period"
+            android:summary="@string/use_double_space_period_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+        <SwitchPreference
             android:key="vibrate_on"
             android:title="@string/vibrate_on_keypress"
-            android:defaultValue="@bool/config_default_vibration_enabled"
-            android:persistent="true" />
-        <CheckBoxPreference
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="@bool/config_default_vibration_enabled" />
+        <SwitchPreference
             android:key="sound_on"
             android:title="@string/sound_on_keypress"
-            android:defaultValue="@bool/config_default_sound_enabled"
-            android:persistent="true" />
-        <CheckBoxPreference
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="@bool/config_default_sound_enabled" />
+        <SwitchPreference
             android:key="popup_on"
             android:title="@string/popup_on_keypress"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="@bool/config_default_key_preview_popup" />
-        <CheckBoxPreference
+        <SwitchPreference
             android:key="pref_voice_input_key"
             android:title="@string/voice_input"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="true" />
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:title="@string/correction_category"
-        android:key="correction_settings">
+    </PreferenceScreen>
+    <ListPreference
+        android:key="pref_keyboard_theme"
+        android:title="@string/keyboard_theme"
+        android:persistent="true"
+        android:entryValues="@array/keyboard_theme_ids"
+        android:entries="@array/keyboard_theme_names" />
+    <PreferenceScreen
+        android:title="@string/settings_screen_multi_lingual"
+        android:key="screen_multi_lingual">
+        <SwitchPreference
+            android:key="pref_show_language_switch_key"
+            android:title="@string/show_language_switch_key"
+            android:summary="@string/show_language_switch_key_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+        <SwitchPreference
+            android:key="pref_include_other_imes_in_language_switch_list"
+            android:dependency="pref_show_language_switch_key"
+            android:title="@string/include_other_imes_in_language_switch_list"
+            android:summary="@string/include_other_imes_in_language_switch_list_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="false" />
+        <PreferenceScreen
+            android:fragment="com.android.inputmethod.latin.settings.AdditionalSubtypeSettings"
+            android:key="custom_input_styles"
+            android:title="@string/custom_input_styles_title" />
+    </PreferenceScreen>
+    <PreferenceScreen
+        android:title="@string/settings_screen_gesture"
+        android:key="screen_gesture">
+        <SwitchPreference
+            android:key="gesture_input"
+            android:title="@string/gesture_input"
+            android:summary="@string/gesture_input_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+        <SwitchPreference
+            android:key="pref_gesture_floating_preview_text"
+            android:dependency="gesture_input"
+            android:title="@string/gesture_floating_preview_text"
+            android:summary="@string/gesture_floating_preview_text_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+        <SwitchPreference
+            android:key="pref_gesture_preview_trail"
+            android:dependency="gesture_input"
+            android:title="@string/gesture_preview_trail"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+    </PreferenceScreen>
+    <PreferenceScreen
+        android:title="@string/settings_screen_correction"
+        android:key="screen_correction">
         <PreferenceScreen
             android:key="edit_personal_dictionary"
             android:title="@string/edit_personal_dictionary">
@@ -67,10 +145,12 @@
                  android:value="@string/dictionary_pack_client_id" />
            </intent>
         </PreferenceScreen>
-        <CheckBoxPreference
+        <SwitchPreference
             android:key="pref_key_block_potentially_offensive"
             android:title="@string/prefs_block_potentially_offensive_title"
             android:summary="@string/prefs_block_potentially_offensive_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="@bool/config_block_potentially_offensive" />
         <ListPreference
@@ -89,119 +169,66 @@
             android:entryValues="@array/prefs_suggestion_visibility_values"
             android:entries="@array/prefs_suggestion_visibilities"
             android:defaultValue="@string/prefs_suggestion_visibility_default_value" />
-        <CheckBoxPreference
+        <SwitchPreference
             android:key="pref_key_use_personalized_dicts"
             android:title="@string/use_personalized_dicts"
             android:summary="@string/use_personalized_dicts_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="true" />
-        <!-- title will be set programmatically to embed application name -->
-        <CheckBoxPreference
-            android:key="pref_enable_metrics_logging"
-            android:summary="@string/enable_metrics_logging_summary"
+        <SwitchPreference
+            android:key="pref_key_use_contacts_dict"
+            android:title="@string/use_contacts_dict"
+            android:summary="@string/use_contacts_dict_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="true" />
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:title="@string/gesture_typing_category"
-        android:key="gesture_typing_settings">
-        <CheckBoxPreference
-            android:key="gesture_input"
-            android:title="@string/gesture_input"
-            android:summary="@string/gesture_input_summary"
-            android:persistent="true"
-            android:defaultValue="true" />
-        <!-- TODO: Move these two options to the advanced settings. -->
-        <CheckBoxPreference
-            android:key="pref_gesture_floating_preview_text"
-            android:dependency="gesture_input"
-            android:title="@string/gesture_floating_preview_text"
-            android:summary="@string/gesture_floating_preview_text_summary"
-            android:persistent="true"
-            android:defaultValue="true" />
-        <CheckBoxPreference
-            android:key="pref_gesture_preview_trail"
-            android:dependency="gesture_input"
-            android:title="@string/gesture_preview_trail"
-            android:persistent="true"
-            android:defaultValue="true" />
-    </PreferenceCategory>
-    <PreferenceCategory
-        android:title="@string/misc_category"
-        android:key="misc_settings">
-        <CheckBoxPreference
+        <SwitchPreference
             android:key="next_word_prediction"
             android:title="@string/bigram_prediction"
             android:summary="@string/bigram_prediction_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
+            android:persistent="true"
+            android:defaultValue="true" />
+    </PreferenceScreen>
+    <PreferenceScreen
+        android:title="@string/settings_screen_advanced"
+        android:key="screen_advanced">
+        <!-- TODO: consolidate key preview dismiss delay with the key preview animation parameters. -->
+        <ListPreference
+            android:key="pref_key_preview_popup_dismiss_delay"
+            android:title="@string/key_preview_popup_dismiss_delay" />
+        <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+            android:key="pref_vibration_duration_settings"
+            android:title="@string/prefs_keypress_vibration_duration_settings"
+            latin:maxValue="@integer/config_max_vibration_duration" />
+        <com.android.inputmethod.latin.settings.SeekBarDialogPreference
+            android:key="pref_keypress_sound_volume"
+            android:title="@string/prefs_keypress_sound_volume_settings"
+            latin:maxValue="100" /> <!-- percent -->
+        <!-- The settigs for showing setup wizard application icon shouldn't be persistent and
+             the default value is added programmatically. -->
+        <SwitchPreference
+            android:key="pref_show_setup_wizard_icon"
+            android:title="@string/show_setup_wizard_icon"
+            android:summary="@string/show_setup_wizard_icon_summary"
+            android:switchTextOn=""
+            android:switchTextOff="" />
+        <!-- title will be set programmatically to embed application name -->
+        <SwitchPreference
+            android:key="pref_enable_metrics_logging"
+            android:summary="@string/enable_metrics_logging_summary"
+            android:switchTextOn=""
+            android:switchTextOff=""
             android:persistent="true"
             android:defaultValue="true" />
         <PreferenceScreen
-            android:key="pref_advanced_settings"
-            android:title="@string/advanced_settings"
-            android:summary="@string/advanced_settings_summary">
-            <CheckBoxPreference
-                android:key="pref_key_use_contacts_dict"
-                android:title="@string/use_contacts_dict"
-                android:summary="@string/use_contacts_dict_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="pref_key_use_double_space_period"
-                android:title="@string/use_double_space_period"
-                android:summary="@string/use_double_space_period_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="pref_show_language_switch_key"
-                android:title="@string/show_language_switch_key"
-                android:summary="@string/show_language_switch_key_summary"
-                android:persistent="true"
-                android:defaultValue="true" />
-            <CheckBoxPreference
-                android:key="pref_include_other_imes_in_language_switch_list"
-                android:dependency="pref_show_language_switch_key"
-                android:title="@string/include_other_imes_in_language_switch_list"
-                android:summary="@string/include_other_imes_in_language_switch_list_summary"
-                android:persistent="true"
-                android:defaultValue="false" />
-            <ListPreference
-                android:key="pref_keyboard_theme"
-                android:title="@string/keyboard_color_scheme"
-                android:persistent="true"
-                android:entryValues="@array/keyboard_theme_ids"
-                android:entries="@array/keyboard_theme_names" />
-            <PreferenceScreen
-                android:fragment="com.android.inputmethod.latin.settings.AdditionalSubtypeSettings"
-                android:key="custom_input_styles"
-                android:title="@string/custom_input_styles_title" />
-            <!-- TODO: consolidate key preview dismiss delay with the key preview animation parameters. -->
-            <ListPreference
-                android:key="pref_key_preview_popup_dismiss_delay"
-                android:title="@string/key_preview_popup_dismiss_delay" />
-            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
-                android:key="pref_vibration_duration_settings"
-                android:title="@string/prefs_keypress_vibration_duration_settings"
-                latin:maxValue="@integer/config_max_vibration_duration" />
-            <com.android.inputmethod.latin.settings.SeekBarDialogPreference
-                android:key="pref_keypress_sound_volume"
-                android:title="@string/prefs_keypress_sound_volume_settings"
-                latin:maxValue="100" /> <!-- percent -->
-            <!-- The settigs for showing setup wizard application icon shouldn't be persistent and
-                 the default value is added programmatically. -->
-            <CheckBoxPreference
-                android:key="pref_show_setup_wizard_icon"
-                android:title="@string/show_setup_wizard_icon"
-                android:summary="@string/show_setup_wizard_icon_summary" />
-        </PreferenceScreen>
-        <PreferenceScreen
-            android:key="send_feedback"
-            android:title="@string/send_feedback" />
-        <PreferenceScreen
-            android:key="about_keyboard" />
-        <PreferenceScreen
-            android:key="debug_settings"
+            android:key="screen_debug"
             android:title="Debug settings"
             android:persistent="true"
             android:defaultValue="false" />
-    </PreferenceCategory>
+    </PreferenceScreen>
 </PreferenceScreen>
diff --git a/java/res/xml/prefs_for_debug.xml b/java/res/xml/prefs_for_debug.xml
index 0bcc5cb..be4c8b3 100644
--- a/java/res/xml/prefs_for_debug.xml
+++ b/java/res/xml/prefs_for_debug.xml
@@ -20,20 +20,26 @@
     android:title="@string/prefs_debug_mode"
     android:key="english_ime_debug_settings"
 >
-    <CheckBoxPreference
+    <SwitchPreference
         android:key="debug_mode"
         android:title="@string/prefs_debug_mode"
+        android:switchTextOn=""
+        android:switchTextOff=""
         android:persistent="true"
         android:defaultValue="false" />
-    <CheckBoxPreference
+    <SwitchPreference
         android:key="force_non_distinct_multitouch"
         android:title="@string/prefs_force_non_distinct_multitouch"
+        android:switchTextOn=""
+        android:switchTextOff=""
         android:persistent="true"
         android:defaultValue="false" />
-    <CheckBoxPreference
+    <SwitchPreference
         android:key="pref_sliding_key_input_preview"
         android:title="@string/sliding_key_input_preview"
         android:summary="@string/sliding_key_input_preview_summary"
+        android:switchTextOn=""
+        android:switchTextOff=""
         android:persistent="true"
         android:defaultValue="true" />
     <com.android.inputmethod.latin.settings.SeekBarDialogPreference
diff --git a/java/res/xml/spell_checker_settings.xml b/java/res/xml/spell_checker_settings.xml
index de67e7f..e3db485 100644
--- a/java/res/xml/spell_checker_settings.xml
+++ b/java/res/xml/spell_checker_settings.xml
@@ -15,11 +15,14 @@
 -->
 
 <PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android">
-  <CheckBoxPreference
-     android:key="pref_spellcheck_use_contacts"
-     android:title="@string/use_contacts_for_spellchecking_option_title"
-     android:summary="@string/use_contacts_for_spellchecking_option_summary"
-     android:persistent="true"
-     android:defaultValue="true" />
+    xmlns:android="http://schemas.android.com/apk/res/android"
+>
+    <SwitchPreference
+        android:key="pref_spellcheck_use_contacts"
+        android:title="@string/use_contacts_for_spellchecking_option_title"
+        android:summary="@string/use_contacts_for_spellchecking_option_summary"
+        android:switchTextOn=""
+        android:switchTextOff=""
+        android:persistent="true"
+        android:defaultValue="true" />
 </PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
index d67d9dc..3925fc6 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java
@@ -305,7 +305,7 @@
         key.onPressed();
         mKeyboardView.invalidateKey(key);
         final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
-        provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+        provider.onHoverEnterTo(key);
         provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
     }
 
@@ -328,6 +328,6 @@
         key.onReleased();
         mKeyboardView.invalidateKey(key);
         final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider();
-        provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
+        provider.onHoverExitFrom(key);
     }
 }
diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
index cb13483..61d066a 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java
@@ -65,6 +65,9 @@
     /** The virtual view identifier for the focused node. */
     private int mAccessibilityFocusedView = UNDEFINED;
 
+    /** The virtual view identifier for the hovering node. */
+    private int mHoveringNodeId = UNDEFINED;
+
     /** The current keyboard view. */
     private final KeyboardView mKeyboardView;
 
@@ -76,7 +79,6 @@
         mKeyCodeDescriptionMapper = KeyCodeDescriptionMapper.getInstance();
         mAccessibilityUtils = AccessibilityUtils.getInstance();
         mKeyboardView = keyboardView;
-        updateParentLocation();
 
         // Since this class is constructed lazily, we might not get a subsequent
         // call to setKeyboard() and therefore need to call it now.
@@ -141,6 +143,28 @@
         return event;
     }
 
+    public void onHoverEnterTo(final Key key) {
+        final int id = getVirtualViewIdOf(key);
+        if (id == View.NO_ID) {
+            return;
+        }
+        // Start hovering on the key. Because our accessibility model is lift-to-type, we should
+        // report the node info without click and long click actions to avoid unnecessary
+        // announcements.
+        mHoveringNodeId = id;
+        // Invalidate the node info of the key.
+        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER);
+    }
+
+    public void onHoverExitFrom(final Key key) {
+        mHoveringNodeId = UNDEFINED;
+        // Invalidate the node info of the key to be able to revert the change we have done
+        // in {@link #onHoverEnterTo(Key)}.
+        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+        sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT);
+    }
+
     /**
      * Returns an {@link AccessibilityNodeInfoCompat} representing a virtual
      * view, i.e. a descendant of the host View, with the given <code>virtualViewId</code> or
@@ -169,10 +193,23 @@
         }
         if (virtualViewId == View.NO_ID) {
             // We are requested to create an AccessibilityNodeInfo describing
-            // this View. Returning an empty info is sufficient for a keyboard.
+            // this View, i.e. the root of the virtual sub-tree.
             final AccessibilityNodeInfoCompat rootInfo =
                     AccessibilityNodeInfoCompat.obtain(mKeyboardView);
             ViewCompat.onInitializeAccessibilityNodeInfo(mKeyboardView, rootInfo);
+            updateParentLocation();
+
+            // Add the virtual children of the root View.
+            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;
         }
 
@@ -200,9 +237,16 @@
         info.setBoundsInScreen(boundsInScreen);
         info.setParent(mKeyboardView);
         info.setSource(mKeyboardView, virtualViewId);
-        info.setBoundsInScreen(boundsInScreen);
-        info.setEnabled(true);
+        info.setEnabled(key.isEnabled());
         info.setVisibleToUser(true);
+        // Don't add ACTION_CLICK and ACTION_LONG_CLOCK actions while hovering on the key.
+        // See {@link #onHoverEnterTo(Key)} and {@link #onHoverExitFrom(Key)}.
+        if (virtualViewId != mHoveringNodeId) {
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+            if (key.isLongPressEnabled()) {
+                info.addAction(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK);
+            }
+        }
 
         if (mAccessibilityFocusedView == virtualViewId) {
             info.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
@@ -241,6 +285,12 @@
             sendAccessibilityEventForKey(
                     key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
             return true;
+        case AccessibilityNodeInfoCompat.ACTION_CLICK:
+            sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_CLICKED);
+            return true;
+        case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:
+            sendAccessibilityEventForKey(key, AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
+            return true;
         default:
             return false;
         }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 9a859bf..8182593 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -35,7 +35,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.InputMethodSubtype;
-import android.widget.TextView;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.MainKeyboardAccessibilityDelegate;
@@ -48,6 +47,7 @@
 import com.android.inputmethod.keyboard.internal.KeyPreviewChoreographer;
 import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.LanguageOnSpacebarHelper;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview;
 import com.android.inputmethod.keyboard.internal.TimerHandler;
@@ -236,16 +236,14 @@
                 R.styleable.MainKeyboardView_gestureFloatingPreviewTextLingerTimeout, 0);
 
         mGestureFloatingTextDrawingPreview = new GestureFloatingTextDrawingPreview(
-                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
-        mDrawingPreviewPlacerView.addPreview(mGestureFloatingTextDrawingPreview);
+                mainKeyboardViewAttr);
+        mGestureFloatingTextDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
 
-        mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(
-                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
-        mDrawingPreviewPlacerView.addPreview(mGestureTrailsDrawingPreview);
+        mGestureTrailsDrawingPreview = new GestureTrailsDrawingPreview(mainKeyboardViewAttr);
+        mGestureTrailsDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
 
-        mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(
-                mDrawingPreviewPlacerView, mainKeyboardViewAttr);
-        mDrawingPreviewPlacerView.addPreview(mSlidingKeyInputDrawingPreview);
+        mSlidingKeyInputDrawingPreview = new SlidingKeyInputDrawingPreview(mainKeyboardViewAttr);
+        mSlidingKeyInputDrawingPreview.setDrawingView(mDrawingPreviewPlacerView);
         mainKeyboardViewAttr.recycle();
 
         mMoreKeysKeyboardContainer = LayoutInflater.from(getContext())
@@ -428,15 +426,6 @@
         windowContentView.addView(mDrawingPreviewPlacerView);
     }
 
-    /**
-     * Returns the enabled state of the key feedback preview
-     * @return whether or not the key feedback preview is enabled
-     * @see #setKeyPreviewPopupEnabled(boolean, int)
-     */
-    public boolean isKeyPreviewPopupEnabled() {
-        return mKeyPreviewDrawParams.isPopupEnabled();
-    }
-
     // Implements {@link DrawingHandler.Callbacks} method.
     @Override
     public void dismissAllKeyPreviews() {
@@ -461,12 +450,9 @@
         }
 
         locatePreviewPlacerView();
-        final TextView previewTextView = mKeyPreviewChoreographer.getKeyPreviewTextView(
-                key, mDrawingPreviewPlacerView);
         getLocationInWindow(mOriginCoords);
-        mKeyPreviewChoreographer.placeKeyPreview(key, previewTextView, keyboard.mIconsSet,
-                mKeyDrawParams, getWidth(), mOriginCoords);
-        mKeyPreviewChoreographer.showKeyPreview(key, previewTextView, isHardwareAccelerated());
+        mKeyPreviewChoreographer.placeKeyPreviewAndShow(key, keyboard.mIconsSet, mKeyDrawParams,
+                getWidth(), mOriginCoords, mDrawingPreviewPlacerView, isHardwareAccelerated());
     }
 
     // Implements {@link TimerHandler.Callbacks} method.
@@ -557,13 +543,25 @@
     }
 
     private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) {
-        if (key.getMoreKeys() == null) {
+        final MoreKeySpec[] moreKeys = key.getMoreKeys();
+        if (moreKeys == null) {
             return null;
         }
         Keyboard moreKeysKeyboard = mMoreKeysKeyboardCache.get(key);
         if (moreKeysKeyboard == null) {
-            moreKeysKeyboard = new MoreKeysKeyboard.Builder(
-                    context, key, this, mKeyPreviewDrawParams).build();
+            // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
+            // {@link KeyPreviewChoreographer#placeKeyPreview(Key,TextView,KeyboardIconsSet,KeyDrawParams,int,int[]},
+            // though there may be some chances that the value is zero. <code>width == 0</code>
+            // will cause zero-division error at
+            // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
+            final boolean singleMoreKeyWithPreview = mKeyPreviewDrawParams.isPopupEnabled()
+                    && !key.noKeyPreview() && moreKeys.length == 1
+                    && mKeyPreviewDrawParams.getVisibleWidth() > 0;
+            final MoreKeysKeyboard.Builder builder = new MoreKeysKeyboard.Builder(
+                    context, key, getKeyboard(), singleMoreKeyWithPreview,
+                    mKeyPreviewDrawParams.getVisibleWidth(),
+                    mKeyPreviewDrawParams.getVisibleHeight(), newLabelPaint(key));
+            moreKeysKeyboard = builder.build();
             mMoreKeysKeyboardCache.put(key, moreKeysKeyboard);
         }
 
@@ -619,7 +617,8 @@
 
         final int[] lastCoords = CoordinateUtils.newInstance();
         tracker.getLastCoordinates(lastCoords);
-        final boolean keyPreviewEnabled = isKeyPreviewPopupEnabled() && !key.noKeyPreview();
+        final boolean keyPreviewEnabled = mKeyPreviewDrawParams.isPopupEnabled()
+                && !key.noKeyPreview();
         // The more keys keyboard is usually horizontally aligned with the center of the parent key.
         // If showMoreKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more
         // keys keyboard is placed at the touch point of the parent key.
@@ -730,6 +729,7 @@
     }
 
     public void onHideWindow() {
+        onDismissMoreKeysPanel();
         final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate;
         if (accessibilityDelegate != null) {
             accessibilityDelegate.onHideWindow();
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index a72f791..353e07c 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -21,7 +21,6 @@
 import android.graphics.drawable.Drawable;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.keyboard.internal.KeyPreviewDrawParams;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -260,32 +259,25 @@
         /**
          * The builder of MoreKeysKeyboard.
          * @param context the context of {@link MoreKeysKeyboardView}.
-         * @param parentKey the {@link Key} that invokes more keys keyboard.
-         * @param parentKeyboardView the {@link KeyboardView} that contains the parentKey.
+         * @param key the {@link Key} that invokes more keys keyboard.
+         * @param keyboard the {@link Keyboard} that contains the parentKey.
+         * @param singleMoreKeyWithPreview true if the <code>key</code> has only one more key
+         *        and key popup preview is enabled.
          * @param keyPreviewDrawParams the parameter to place key preview.
+         * @param paintToMeasure the {@link Paint} object to measure a more key width
          */
-        public Builder(final Context context, final Key parentKey,
-                final MainKeyboardView parentKeyboardView,
-                final KeyPreviewDrawParams keyPreviewDrawParams) {
+        public Builder(final Context context, final Key key, final Keyboard keyboard,
+                final boolean singleMoreKeyWithPreview, final int keyPreviewVisibleWidth,
+                final int keyPreviewVisibleHeight, final Paint paintToMeasure) {
             super(context, new MoreKeysKeyboardParams());
-            final Keyboard parentKeyboard = parentKeyboardView.getKeyboard();
-            load(parentKeyboard.mMoreKeysTemplate, parentKeyboard.mId);
+            load(keyboard.mMoreKeysTemplate, keyboard.mId);
 
             // TODO: More keys keyboard's vertical gap is currently calculated heuristically.
             // Should revise the algorithm.
-            mParams.mVerticalGap = parentKeyboard.mVerticalGap / 2;
-            mParentKey = parentKey;
+            mParams.mVerticalGap = keyboard.mVerticalGap / 2;
+            mParentKey = key;
 
-            final MoreKeySpec[] moreKeys = parentKey.getMoreKeys();
-            final int width, height;
-            // {@link KeyPreviewDrawParams#mPreviewVisibleWidth} should have been set at
-            // {@link MainKeyboardView#showKeyPreview(PointerTracker}, though there may be
-            // some chances that the value is zero. <code>width == 0</code> will cause
-            // zero-division error at
-            // {@link MoreKeysKeyboardParams#setParameters(int,int,int,int,int,int,boolean,int)}.
-            final boolean singleMoreKeyWithPreview = parentKeyboardView.isKeyPreviewPopupEnabled()
-                    && !parentKey.noKeyPreview() && moreKeys.length == 1
-                    && keyPreviewDrawParams.getVisibleWidth() > 0;
+            final int keyWidth, rowHeight;
             if (singleMoreKeyWithPreview) {
                 // Use pre-computed width and height if this more keys keyboard has only one key to
                 // mitigate visual flicker between key preview and more keys keyboard.
@@ -294,29 +286,28 @@
                 // left/right/top paddings. The bottom paddings of both backgrounds don't need to
                 // be considered because the vertical positions of both backgrounds were already
                 // adjusted with their bottom paddings deducted.
-                width = keyPreviewDrawParams.getVisibleWidth();
-                height = keyPreviewDrawParams.getVisibleHeight() + mParams.mVerticalGap;
+                keyWidth = keyPreviewVisibleWidth;
+                rowHeight = keyPreviewVisibleHeight + mParams.mVerticalGap;
             } else {
                 final float padding = context.getResources().getDimension(
                         R.dimen.config_more_keys_keyboard_key_horizontal_padding)
-                        + (parentKey.hasLabelsInMoreKeys()
+                        + (key.hasLabelsInMoreKeys()
                                 ? mParams.mDefaultKeyWidth * LABEL_PADDING_RATIO : 0.0f);
-                width = getMaxKeyWidth(parentKey, mParams.mDefaultKeyWidth, padding,
-                        parentKeyboardView.newLabelPaint(parentKey));
-                height = parentKeyboard.mMostCommonKeyHeight;
+                keyWidth = getMaxKeyWidth(key, mParams.mDefaultKeyWidth, padding, paintToMeasure);
+                rowHeight = keyboard.mMostCommonKeyHeight;
             }
             final int dividerWidth;
-            if (parentKey.needsDividersInMoreKeys()) {
+            if (key.needsDividersInMoreKeys()) {
                 mDivider = mResources.getDrawable(R.drawable.more_keys_divider);
-                dividerWidth = (int)(width * DIVIDER_RATIO);
+                dividerWidth = (int)(keyWidth * DIVIDER_RATIO);
             } else {
                 mDivider = null;
                 dividerWidth = 0;
             }
-            mParams.setParameters(moreKeys.length, parentKey.getMoreKeysColumn(),
-                    width, height, parentKey.getX() + parentKey.getWidth() / 2,
-                    parentKeyboard.mId.mWidth, parentKey.isFixedColumnOrderMoreKeys(),
-                    dividerWidth);
+            final MoreKeySpec[] moreKeys = key.getMoreKeys();
+            mParams.setParameters(moreKeys.length, key.getMoreKeysColumn(), keyWidth, rowHeight,
+                    key.getX() + key.getWidth() / 2, keyboard.mId.mWidth,
+                    key.isFixedColumnOrderMoreKeys(), dividerWidth);
         }
 
         private static int getMaxKeyWidth(final Key parentKey, final int minKeyWidth,
diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
index a34dbef..8010a3e 100644
--- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java
@@ -21,6 +21,7 @@
 import android.util.AttributeSet;
 import android.view.GestureDetector;
 import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.KeyboardAccessibilityDelegate;
@@ -106,6 +107,12 @@
         }
     }
 
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(final AccessibilityEvent event) {
+        // Don't populate accessibility event with all Emoji keys.
+        return true;
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
index 3a72aed..a194f3d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/AbstractDrawingPreview.java
@@ -27,16 +27,19 @@
  * SlidingKeyInputDrawingPreview.
  */
 public abstract class AbstractDrawingPreview {
-    private final View mDrawingView;
+    private View mDrawingView;
     private boolean mPreviewEnabled;
     private boolean mHasValidGeometry;
 
-    protected AbstractDrawingPreview(final View drawingView) {
+    public void setDrawingView(final DrawingPreviewPlacerView drawingView) {
         mDrawingView = drawingView;
+        drawingView.addPreview(this);
     }
 
-    protected final View getDrawingView() {
-        return mDrawingView;
+    protected void invalidateDrawingView() {
+        if (mDrawingView != null) {
+            mDrawingView.invalidate();
+        }
     }
 
     protected final boolean isPreviewEnabled() {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
index 3b4c434..a5d47ad 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java
@@ -46,7 +46,9 @@
     }
 
     public void addPreview(final AbstractDrawingPreview preview) {
-        mPreviews.add(preview);
+        if (mPreviews.indexOf(preview) < 0) {
+            mPreviews.add(preview);
+        }
     }
 
     public void setKeyboardViewGeometry(final int[] originCoords, final int width,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
index 2fa7030..fd84856 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingTextDrawingPreview.java
@@ -23,7 +23,6 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.text.TextUtils;
-import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.R;
@@ -49,6 +48,7 @@
         public final float mGesturePreviewHorizontalPadding;
         public final float mGesturePreviewVerticalPadding;
         public final float mGesturePreviewRoundRadius;
+        public final int mDisplayWidth;
 
         private final int mGesturePreviewTextSize;
         private final int mGesturePreviewTextColor;
@@ -72,6 +72,7 @@
                     R.styleable.MainKeyboardView_gestureFloatingPreviewVerticalPadding, 0.0f);
             mGesturePreviewRoundRadius = mainKeyboardViewAttr.getDimension(
                     R.styleable.MainKeyboardView_gestureFloatingPreviewRoundRadius, 0.0f);
+            mDisplayWidth = mainKeyboardViewAttr.getResources().getDisplayMetrics().widthPixels;
 
             final Paint textPaint = getTextPaint();
             final Rect textRect = new Rect();
@@ -100,9 +101,8 @@
     private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     private final int[] mLastPointerCoords = CoordinateUtils.newInstance();
 
-    public GestureFloatingTextDrawingPreview(final View drawingView, final TypedArray typedArray) {
-        super(drawingView);
-        mParams = new GesturePreviewTextParams(typedArray);
+    public GestureFloatingTextDrawingPreview(final TypedArray mainKeyboardViewAttr) {
+        mParams = new GesturePreviewTextParams(mainKeyboardViewAttr);
     }
 
     @Override
@@ -149,7 +149,7 @@
      */
     protected void updatePreviewPosition() {
         if (mSuggestedWords.isEmpty() || TextUtils.isEmpty(mSuggestedWords.getWord(0))) {
-            getDrawingView().invalidate();
+            invalidateDrawingView();
             return;
         }
         final String text = mSuggestedWords.getWord(0);
@@ -163,10 +163,9 @@
         final float rectWidth = textWidth + hPad * 2.0f;
         final float rectHeight = textHeight + vPad * 2.0f;
 
-        final int displayWidth = getDrawingView().getResources().getDisplayMetrics().widthPixels;
         final float rectX = Math.min(
                 Math.max(CoordinateUtils.x(mLastPointerCoords) - rectWidth / 2.0f, 0.0f),
-                displayWidth - rectWidth);
+                mParams.mDisplayWidth - rectWidth);
         final float rectY = CoordinateUtils.y(mLastPointerCoords)
                 - mParams.mGesturePreviewTextOffset - rectHeight;
         rectangle.set(rectX, rectY, rectX + rectWidth, rectY + rectHeight);
@@ -174,6 +173,6 @@
         mPreviewTextX = (int)(rectX + hPad + textWidth / 2.0f);
         mPreviewTextY = (int)(rectY + vPad) + textHeight;
         // TODO: Should narrow the invalidate region.
-        getDrawingView().invalidate();
+        invalidateDrawingView();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
index 72628e3..f7bd7ef 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java
@@ -24,17 +24,15 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
-import android.os.Message;
+import android.os.Handler;
 import android.util.SparseArray;
-import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
-import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper;
 
 /**
  * Draw preview graphics of multiple gesture trails during gesture input.
  */
-public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview {
+public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview implements Runnable {
     private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = new SparseArray<>();
     private final GestureTrailDrawingParams mDrawingParams;
     private final Paint mGesturePaint;
@@ -47,45 +45,10 @@
     private final Rect mDirtyRect = new Rect();
     private final Rect mGestureTrailBoundsRect = new Rect(); // per trail
 
-    private final DrawingHandler mDrawingHandler;
+    private final Handler mDrawingHandler = new Handler();
 
-    private static final class DrawingHandler
-            extends LeakGuardHandlerWrapper<GestureTrailsDrawingPreview> {
-        private static final int MSG_UPDATE_GESTURE_TRAIL = 0;
-
-        private final GestureTrailDrawingParams mDrawingParams;
-
-        public DrawingHandler(final GestureTrailsDrawingPreview ownerInstance,
-                final GestureTrailDrawingParams drawingParams) {
-            super(ownerInstance);
-            mDrawingParams = drawingParams;
-        }
-
-        @Override
-        public void handleMessage(final Message msg) {
-            final GestureTrailsDrawingPreview preview = getOwnerInstance();
-            if (preview == null) {
-                return;
-            }
-            switch (msg.what) {
-            case MSG_UPDATE_GESTURE_TRAIL:
-                preview.getDrawingView().invalidate();
-                break;
-            }
-        }
-
-        public void postUpdateGestureTrailPreview() {
-            removeMessages(MSG_UPDATE_GESTURE_TRAIL);
-            sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_TRAIL),
-                    mDrawingParams.mUpdateInterval);
-        }
-    }
-
-    public GestureTrailsDrawingPreview(final View drawingView,
-            final TypedArray mainKeyboardViewAttr) {
-        super(drawingView);
+    public GestureTrailsDrawingPreview(final TypedArray mainKeyboardViewAttr) {
         mDrawingParams = new GestureTrailDrawingParams(mainKeyboardViewAttr);
-        mDrawingHandler = new DrawingHandler(this, mDrawingParams);
         final Paint gesturePaint = new Paint();
         gesturePaint.setAntiAlias(true);
         gesturePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
@@ -153,6 +116,12 @@
         return needsUpdatingGestureTrail;
     }
 
+    @Override
+    public void run() {
+        // Update preview.
+        invalidateDrawingView();
+    }
+
     /**
      * Draws the preview
      * @param canvas The canvas where the preview is drawn.
@@ -167,7 +136,8 @@
         final boolean needsUpdatingGestureTrail = drawGestureTrails(
                 mOffscreenCanvas, mGesturePaint, mDirtyRect);
         if (needsUpdatingGestureTrail) {
-            mDrawingHandler.postUpdateGestureTrailPreview();
+            mDrawingHandler.removeCallbacks(this);
+            mDrawingHandler.postDelayed(this, mDrawingParams.mUpdateInterval);
         }
         // Transfer offscreen buffer to screen.
         if (!mDirtyRect.isEmpty()) {
@@ -199,6 +169,6 @@
         trail.addStroke(tracker.getGestureStrokeDrawingPoints(), tracker.getDownTime());
 
         // TODO: Should narrow the invalidate region.
-        getDrawingView().invalidate();
+        invalidateDrawingView();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
index 605519b..6fc300b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java
@@ -21,17 +21,12 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
-import android.widget.TextView;
 
 import com.android.inputmethod.keyboard.Key;
-import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.CoordinateUtils;
 import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 
@@ -46,10 +41,11 @@
  * - how key previews should be shown and dismissed.
  */
 public final class KeyPreviewChoreographer {
-    // Free {@link TextView} pool that can be used for key preview.
-    private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = new ArrayDeque<>();
-    // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview.
-    private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = new HashMap<>();
+    // Free {@link KeyPreviewView} pool that can be used for key preview.
+    private final ArrayDeque<KeyPreviewView> mFreeKeyPreviewViews = new ArrayDeque<>();
+    // Map from {@link Key} to {@link KeyPreviewView} that is currently being displayed as key
+    // preview.
+    private final HashMap<Key,KeyPreviewView> mShowingKeyPreviewViews = new HashMap<>();
 
     private final KeyPreviewDrawParams mParams;
 
@@ -57,32 +53,28 @@
         mParams = params;
     }
 
-    public TextView getKeyPreviewTextView(final Key key, final ViewGroup placerView) {
-        TextView previewTextView = mShowingKeyPreviewTextViews.remove(key);
-        if (previewTextView != null) {
-            return previewTextView;
+    public KeyPreviewView getKeyPreviewView(final Key key, final ViewGroup placerView) {
+        KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.remove(key);
+        if (keyPreviewView != null) {
+            return keyPreviewView;
         }
-        previewTextView = mFreeKeyPreviewTextViews.poll();
-        if (previewTextView != null) {
-            return previewTextView;
+        keyPreviewView = mFreeKeyPreviewViews.poll();
+        if (keyPreviewView != null) {
+            return keyPreviewView;
         }
         final Context context = placerView.getContext();
-        if (mParams.mLayoutId != 0) {
-            previewTextView = (TextView)LayoutInflater.from(context)
-                    .inflate(mParams.mLayoutId, null);
-        } else {
-            previewTextView = new TextView(context);
-        }
-        placerView.addView(previewTextView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
-        return previewTextView;
+        keyPreviewView = new KeyPreviewView(context, null /* attrs */);
+        keyPreviewView.setBackgroundResource(mParams.mPreviewBackgroundResId);
+        placerView.addView(keyPreviewView, ViewLayoutUtils.newLayoutParam(placerView, 0, 0));
+        return keyPreviewView;
     }
 
     public boolean isShowingKeyPreview(final Key key) {
-        return mShowingKeyPreviewTextViews.containsKey(key);
+        return mShowingKeyPreviewViews.containsKey(key);
     }
 
     public void dismissAllKeyPreviews() {
-        for (final Key key : new HashSet<>(mShowingKeyPreviewTextViews.keySet())) {
+        for (final Key key : new HashSet<>(mShowingKeyPreviewViews.keySet())) {
             dismissKeyPreview(key, false /* withAnimation */);
         }
     }
@@ -91,11 +83,11 @@
         if (key == null) {
             return;
         }
-        final TextView previewTextView = mShowingKeyPreviewTextViews.get(key);
-        if (previewTextView == null) {
+        final KeyPreviewView keyPreviewView = mShowingKeyPreviewViews.get(key);
+        if (keyPreviewView == null) {
             return;
         }
-        final Object tag = previewTextView.getTag();
+        final Object tag = keyPreviewView.getTag();
         if (withAnimation) {
             if (tag instanceof KeyPreviewAnimations) {
                 final KeyPreviewAnimations animation = (KeyPreviewAnimations)tag;
@@ -104,105 +96,76 @@
             }
         }
         // Dismiss preview without animation.
-        mShowingKeyPreviewTextViews.remove(key);
+        mShowingKeyPreviewViews.remove(key);
         if (tag instanceof Animator) {
             ((Animator)tag).cancel();
         }
-        previewTextView.setTag(null);
-        previewTextView.setVisibility(View.INVISIBLE);
-        mFreeKeyPreviewTextViews.add(previewTextView);
+        keyPreviewView.setTag(null);
+        keyPreviewView.setVisibility(View.INVISIBLE);
+        mFreeKeyPreviewViews.add(keyPreviewView);
     }
 
-    // Background state set
-    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
-        { // STATE_MIDDLE
-            {},
-            { R.attr.state_has_morekeys }
-        },
-        { // STATE_LEFT
-            { R.attr.state_left_edge },
-            { R.attr.state_left_edge, R.attr.state_has_morekeys }
-        },
-        { // STATE_RIGHT
-            { R.attr.state_right_edge },
-            { R.attr.state_right_edge, R.attr.state_has_morekeys }
-        }
-    };
-    private static final int STATE_MIDDLE = 0;
-    private static final int STATE_LEFT = 1;
-    private static final int STATE_RIGHT = 2;
-    private static final int STATE_NORMAL = 0;
-    private static final int STATE_HAS_MOREKEYS = 1;
+    public void placeKeyPreviewAndShow(final Key key, final KeyboardIconsSet iconsSet,
+            final KeyDrawParams drawParams, final int keyboardViewWidth, final int[] keyboardOrigin,
+            final ViewGroup placerView, final boolean withAnimation) {
+        final KeyPreviewView keyPreviewView = getKeyPreviewView(key, placerView);
+        placeKeyPreview(
+                key, keyPreviewView, iconsSet, drawParams, keyboardViewWidth, keyboardOrigin);
+        showKeyPreview(key, keyPreviewView, withAnimation);
+    }
 
-    public void placeKeyPreview(final Key key, final TextView previewTextView,
+    private void placeKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
             final KeyboardIconsSet iconsSet, final KeyDrawParams drawParams,
             final int keyboardViewWidth, final int[] originCoords) {
-        previewTextView.setTextColor(drawParams.mPreviewTextColor);
-        final Drawable background = previewTextView.getBackground();
-        final String label = key.getPreviewLabel();
-        // What we show as preview should match what we show on a key top in onDraw().
-        if (label != null) {
-            // TODO Should take care of temporaryShiftLabel here.
-            previewTextView.setCompoundDrawables(null, null, null, null);
-            previewTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                    key.selectPreviewTextSize(drawParams));
-            previewTextView.setTypeface(key.selectPreviewTypeface(drawParams));
-            previewTextView.setText(label);
-        } else {
-            previewTextView.setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
-            previewTextView.setText(null);
-        }
-
-        previewTextView.measure(
+        keyPreviewView.setPreviewVisual(key, iconsSet, drawParams);
+        keyPreviewView.measure(
                 ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-        mParams.setGeometry(previewTextView);
-        final int previewWidth = previewTextView.getMeasuredWidth();
+        mParams.setGeometry(keyPreviewView);
+        final int previewWidth = keyPreviewView.getMeasuredWidth();
         final int previewHeight = mParams.mPreviewHeight;
         final int keyDrawWidth = key.getDrawWidth();
         // The key preview is horizontally aligned with the center of the visible part of the
         // parent key. If it doesn't fit in this {@link KeyboardView}, it is moved inward to fit and
         // the left/right background is used if such background is specified.
-        final int statePosition;
+        final int keyPreviewPosition;
         int previewX = key.getDrawX() - (previewWidth - keyDrawWidth) / 2
                 + CoordinateUtils.x(originCoords);
         if (previewX < 0) {
             previewX = 0;
-            statePosition = STATE_LEFT;
+            keyPreviewPosition = KeyPreviewView.POSITION_LEFT;
         } else if (previewX > keyboardViewWidth - previewWidth) {
             previewX = keyboardViewWidth - previewWidth;
-            statePosition = STATE_RIGHT;
+            keyPreviewPosition = KeyPreviewView.POSITION_RIGHT;
         } else {
-            statePosition = STATE_MIDDLE;
+            keyPreviewPosition = KeyPreviewView.POSITION_MIDDLE;
         }
+        final boolean hasMoreKeys = (key.getMoreKeys() != null);
+        keyPreviewView.setPreviewBackground(hasMoreKeys, keyPreviewPosition);
         // The key preview is placed vertically above the top edge of the parent key with an
         // arbitrary offset.
         final int previewY = key.getY() - previewHeight + mParams.mPreviewOffset
                 + CoordinateUtils.y(originCoords);
 
-        if (background != null) {
-            final int hasMoreKeys = (key.getMoreKeys() != null) ? STATE_HAS_MOREKEYS : STATE_NORMAL;
-            background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[statePosition][hasMoreKeys]);
-        }
         ViewLayoutUtils.placeViewAt(
-                previewTextView, previewX, previewY, previewWidth, previewHeight);
-        previewTextView.setPivotX(previewWidth / 2.0f);
-        previewTextView.setPivotY(previewHeight);
+                keyPreviewView, previewX, previewY, previewWidth, previewHeight);
+        keyPreviewView.setPivotX(previewWidth / 2.0f);
+        keyPreviewView.setPivotY(previewHeight);
     }
 
-    public void showKeyPreview(final Key key, final TextView previewTextView,
+    private void showKeyPreview(final Key key, final KeyPreviewView keyPreviewView,
             final boolean withAnimation) {
         if (!withAnimation) {
-            previewTextView.setVisibility(View.VISIBLE);
-            mShowingKeyPreviewTextViews.put(key, previewTextView);
+            keyPreviewView.setVisibility(View.VISIBLE);
+            mShowingKeyPreviewViews.put(key, keyPreviewView);
             return;
         }
 
         // Show preview with animation.
-        final Animator showUpAnimation = createShowUpAniation(key, previewTextView);
-        final Animator dismissAnimation = createDismissAnimation(key, previewTextView);
+        final Animator showUpAnimation = createShowUpAniation(key, keyPreviewView);
+        final Animator dismissAnimation = createDismissAnimation(key, keyPreviewView);
         final KeyPreviewAnimations animation = new KeyPreviewAnimations(
                 showUpAnimation, dismissAnimation);
-        previewTextView.setTag(animation);
+        keyPreviewView.setTag(animation);
         animation.startShowUp();
     }
 
@@ -212,13 +175,13 @@
     private static final DecelerateInterpolator DECELERATE_INTERPOLATOR =
             new DecelerateInterpolator();
 
-    private Animator createShowUpAniation(final Key key, final TextView previewTextView) {
+    private Animator createShowUpAniation(final Key key, final KeyPreviewView keyPreviewView) {
         // TODO: Optimization for no scale animation and no duration.
         final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_X, mParams.getShowUpStartScale(),
+                keyPreviewView, View.SCALE_X, mParams.getShowUpStartScale(),
                 KEY_PREVIEW_SHOW_UP_END_SCALE);
         final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_Y, mParams.getShowUpStartScale(),
+                keyPreviewView, View.SCALE_Y, mParams.getShowUpStartScale(),
                 KEY_PREVIEW_SHOW_UP_END_SCALE);
         final AnimatorSet showUpAnimation = new AnimatorSet();
         showUpAnimation.play(scaleXAnimation).with(scaleYAnimation);
@@ -227,18 +190,18 @@
         showUpAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(final Animator animation) {
-                showKeyPreview(key, previewTextView, false /* withAnimation */);
+                showKeyPreview(key, keyPreviewView, false /* withAnimation */);
             }
         });
         return showUpAnimation;
     }
 
-    private Animator createDismissAnimation(final Key key, final TextView previewTextView) {
+    private Animator createDismissAnimation(final Key key, final KeyPreviewView keyPreviewView) {
         // TODO: Optimization for no scale animation and no duration.
         final ObjectAnimator scaleXAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_X, mParams.getDismissEndScale());
+                keyPreviewView, View.SCALE_X, mParams.getDismissEndScale());
         final ObjectAnimator scaleYAnimation = ObjectAnimator.ofFloat(
-                previewTextView, View.SCALE_Y, mParams.getDismissEndScale());
+                keyPreviewView, View.SCALE_Y, mParams.getDismissEndScale());
         final AnimatorSet dismissAnimation = new AnimatorSet();
         dismissAnimation.play(scaleXAnimation).with(scaleYAnimation);
         final int dismissDuration = Math.min(
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
index 37e5c88..68c9831 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewDrawParams.java
@@ -23,9 +23,9 @@
 
 public final class KeyPreviewDrawParams {
     // XML attributes of {@link MainKeyboardView}.
-    public final int mLayoutId;
     public final int mPreviewOffset;
     public final int mPreviewHeight;
+    public final int mPreviewBackgroundResId;
     private int mShowUpDuration;
     private int mDismissDuration;
     private float mShowUpStartScale;
@@ -63,13 +63,10 @@
                 R.styleable.MainKeyboardView_keyPreviewOffset, 0);
         mPreviewHeight = mainKeyboardViewAttr.getDimensionPixelSize(
                 R.styleable.MainKeyboardView_keyPreviewHeight, 0);
+        mPreviewBackgroundResId = mainKeyboardViewAttr.getResourceId(
+                R.styleable.MainKeyboardView_keyPreviewBackground, 0);
         mLingerTimeout = mainKeyboardViewAttr.getInt(
                 R.styleable.MainKeyboardView_keyPreviewLingerTimeout, 0);
-        mLayoutId = mainKeyboardViewAttr.getResourceId(
-                R.styleable.MainKeyboardView_keyPreviewLayout, 0);
-        if (mLayoutId == 0) {
-            mShowPopup = false;
-        }
     }
 
     public void setVisibleOffset(final int previewVisibleOffset) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
new file mode 100644
index 0000000..360faf8
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewView.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package com.android.inputmethod.keyboard.internal;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.widget.TextView;
+
+import com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.latin.R;
+
+/**
+ * The pop up key preview view.
+ */
+public class KeyPreviewView extends TextView {
+    public static final int POSITION_MIDDLE = 0;
+    public static final int POSITION_LEFT = 1;
+    public static final int POSITION_RIGHT = 2;
+
+    public KeyPreviewView(final Context context, final AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public KeyPreviewView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        setGravity(Gravity.CENTER);
+    }
+
+    public void setPreviewVisual(final Key key, final KeyboardIconsSet iconsSet,
+            final KeyDrawParams drawParams) {
+        // What we show as preview should match what we show on a key top in onDraw().
+        final int iconId = key.getIconId();
+        if (iconId != KeyboardIconsSet.ICON_UNDEFINED) {
+            setCompoundDrawables(null, null, null, key.getPreviewIcon(iconsSet));
+            setText(null);
+            return;
+        }
+
+        setCompoundDrawables(null, null, null, null);
+        setTextColor(drawParams.mPreviewTextColor);
+        setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectPreviewTextSize(drawParams));
+        setTypeface(key.selectPreviewTypeface(drawParams));
+        // TODO Should take care of temporaryShiftLabel here.
+        setText(key.getPreviewLabel());
+    }
+
+    // Background state set
+    private static final int[][][] KEY_PREVIEW_BACKGROUND_STATE_TABLE = {
+        { // POSITION_MIDDLE
+            {},
+            { R.attr.state_has_morekeys }
+        },
+        { // POSITION_LEFT
+            { R.attr.state_left_edge },
+            { R.attr.state_left_edge, R.attr.state_has_morekeys }
+        },
+        { // POSITION_RIGHT
+            { R.attr.state_right_edge },
+            { R.attr.state_right_edge, R.attr.state_has_morekeys }
+        }
+    };
+    private static final int STATE_NORMAL = 0;
+    private static final int STATE_HAS_MOREKEYS = 1;
+
+    public void setPreviewBackground(final boolean hasMoreKeys, final int position) {
+        final Drawable background = getBackground();
+        if (background == null) {
+            return;
+        }
+        final int hasMoreKeysState = hasMoreKeys ? STATE_HAS_MOREKEYS : STATE_NORMAL;
+        background.setState(KEY_PREVIEW_BACKGROUND_STATE_TABLE[position][hasMoreKeysState]);
+    }
+}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
index 76cb891..ef4c74d 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputDrawingPreview.java
@@ -20,7 +20,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
-import android.view.View;
 
 import com.android.inputmethod.keyboard.PointerTracker;
 import com.android.inputmethod.latin.R;
@@ -28,6 +27,11 @@
 
 /**
  * Draw rubber band preview graphics during sliding key input.
+ *
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputPreviewColor
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputPreviewWidth
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputPreviewBodyRatio
+ * @attr ref R.styleable#MainKeyboardView_slidingKeyInputPreviewShadowRatio
  */
 public final class SlidingKeyInputDrawingPreview extends AbstractDrawingPreview {
     private final float mPreviewBodyRadius;
@@ -40,9 +44,7 @@
     private final RoundedLine mRoundedLine = new RoundedLine();
     private final Paint mPaint = new Paint();
 
-    public SlidingKeyInputDrawingPreview(final View drawingView,
-            final TypedArray mainKeyboardViewAttr) {
-        super(drawingView);
+    public SlidingKeyInputDrawingPreview(final TypedArray mainKeyboardViewAttr) {
         final int previewColor = mainKeyboardViewAttr.getColor(
                 R.styleable.MainKeyboardView_slidingKeyInputPreviewColor, 0);
         final float previewRadius = mainKeyboardViewAttr.getDimension(
@@ -69,7 +71,7 @@
 
     public void dismissSlidingKeyInputPreview() {
         mShowsSlidingKeyInputPreview = false;
-        getDrawingView().invalidate();
+        invalidateDrawingView();
     }
 
     /**
@@ -99,6 +101,6 @@
         tracker.getDownCoordinates(mPreviewFrom);
         tracker.getLastCoordinates(mPreviewTo);
         mShowsSlidingKeyInputPreview = true;
-        getDrawingView().invalidate();
+        invalidateDrawingView();
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
index 4a28a24..8c4870d 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java
@@ -468,15 +468,19 @@
                 isValid, timeStampInSeconds, mDistracterFilter);
     }
 
-    public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo,
-            final String committedWord) {
-        final ExpandableBinaryDictionary userHistoryDictionary =
-                mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY);
-        if (userHistoryDictionary != null) {
-            userHistoryDictionary.removeNgramDynamically(prevWordsInfo, committedWord);
+    private void removeWord(final String dictName, final String word) {
+        final ExpandableBinaryDictionary dictionary = mDictionaries.getSubDict(dictName);
+        if (dictionary != null) {
+            dictionary.removeUnigramEntryDynamically(word);
         }
     }
 
+    public void removeWordFromPersonalizedDicts(final String word) {
+        removeWord(Dictionary.TYPE_USER_HISTORY, word);
+        removeWord(Dictionary.TYPE_PERSONALIZATION, word);
+        removeWord(Dictionary.TYPE_CONTEXTUAL, word);
+    }
+
     // TODO: Revise the way to fusion suggestion results.
     public SuggestionResults getSuggestionResults(final WordComposer composer,
             final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index b1966bf..6199c7d 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -311,6 +311,27 @@
     }
 
     /**
+     * Dynamically remove the unigram entry from the dictionary.
+     */
+    public void removeUnigramEntryDynamically(final String word) {
+        reloadDictionaryIfRequired();
+        asyncExecuteTaskWithWriteLock(new Runnable() {
+            @Override
+            public void run() {
+                if (mBinaryDictionary == null) {
+                    return;
+                }
+                runGCIfRequiredLocked(true /* mindsBlockByGC */);
+                if (!mBinaryDictionary.removeUnigramEntry(word)) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Cannot remove unigram entry: " + word);
+                    }
+                }
+            }
+        });
+    }
+
+    /**
      * Adds n-gram information of a word to the dictionary. May overwrite an existing entry.
      */
     public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
@@ -341,6 +362,7 @@
     /**
      * Dynamically remove the n-gram entry in the dictionary.
      */
+    @UsedForTesting
     public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) {
         reloadDictionaryIfRequired();
         asyncExecuteTaskWithWriteLock(new Runnable() {
diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java
index e1ae3df..ebe4361 100644
--- a/java/src/com/android/inputmethod/latin/InputAttributes.java
+++ b/java/src/com/android/inputmethod/latin/InputAttributes.java
@@ -41,6 +41,7 @@
     final public boolean mShouldShowSuggestions;
     final public boolean mApplicationSpecifiedCompletionOn;
     final public boolean mShouldInsertSpacesAutomatically;
+    final public boolean mShouldShowVoiceInputKey;
     final private int mInputType;
     final private EditorInfo mEditorInfo;
     final private String mPackageNameForPrivateImeOptions;
@@ -74,6 +75,7 @@
             mInputTypeNoAutoCorrect = false;
             mApplicationSpecifiedCompletionOn = false;
             mShouldInsertSpacesAutomatically = false;
+            mShouldShowVoiceInputKey = false;
             return;
         }
         // inputClass == InputType.TYPE_CLASS_TEXT
@@ -99,6 +101,12 @@
 
         mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType);
 
+        final boolean noMicrophone = mIsPasswordField
+                || InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS == variation
+                || InputType.TYPE_TEXT_VARIATION_URI == variation
+                || hasNoMicrophoneKeyOption();
+        mShouldShowVoiceInputKey = !noMicrophone;
+
         // If it's a browser edit field and auto correct is not ON explicitly, then
         // disable auto correction, but keep suggestions on.
         // If NO_SUGGESTIONS is set, don't do prediction.
@@ -119,7 +127,7 @@
         return editorInfo.inputType == mInputType;
     }
 
-    public boolean hasNoMicrophoneKeyOption() {
+    private boolean hasNoMicrophoneKeyOption() {
         @SuppressWarnings("deprecation")
         final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions(
                 null, NO_MICROPHONE_COMPAT, mEditorInfo);
diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
index de95b97..9462c38 100644
--- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
+++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java
@@ -884,6 +884,9 @@
                 final String rejectedSuggestion = mWordComposer.getTypedWord();
                 mWordComposer.reset();
                 mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion);
+                if (!TextUtils.isEmpty(rejectedSuggestion)) {
+                    mDictionaryFacilitator.removeWordFromPersonalizedDicts(rejectedSuggestion);
+                }
             } else {
                 mWordComposer.processEvent(inputTransaction.mEvent);
             }
@@ -1187,6 +1190,8 @@
                 Log.w(TAG, "Called updateSuggestionsOrPredictions but suggestions were not "
                         + "requested!");
             }
+            // Clear the suggestions strip.
+            mSuggestionStripViewAccessor.showSuggestionStrip(SuggestedWords.EMPTY);
             return;
         }
 
@@ -1363,7 +1368,6 @@
      * @param inputTransaction The transaction in progress.
      */
     private void revertCommit(final InputTransaction inputTransaction) {
-        final PrevWordsInfo prevWordsInfo = mLastComposedWord.mPrevWordsInfo;
         final CharSequence originallyTypedWord = mLastComposedWord.mTypedWord;
         final CharSequence committedWord = mLastComposedWord.mCommittedWord;
         final String committedWordString = committedWord.toString();
@@ -1385,8 +1389,8 @@
             }
         }
         mConnection.deleteSurroundingText(deleteLength, 0);
-        if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) {
-            mDictionaryFacilitator.cancelAddingUserHistory(prevWordsInfo, committedWordString);
+        if (!TextUtils.isEmpty(committedWord)) {
+            mDictionaryFacilitator.removeWordFromPersonalizedDicts(committedWordString);
         }
         final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString;
         final SpannableString textToCommit = new SpannableString(stringToCommit);
diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
index 845ddb3..c17e868 100644
--- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java
@@ -21,14 +21,13 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Process;
-import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
+import android.preference.TwoStatePreference;
 
-import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver;
 import com.android.inputmethod.latin.DictionaryFacilitator;
 import com.android.inputmethod.latin.R;
@@ -57,7 +56,7 @@
     public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
 
     private boolean mServiceNeedsRestart = false;
-    private CheckBoxPreference mDebugMode;
+    private TwoStatePreference mDebugMode;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -107,7 +106,7 @@
                         res, R.fraction.config_key_preview_dismiss_end_scale));
 
         mServiceNeedsRestart = false;
-        mDebugMode = (CheckBoxPreference) findPreference(PREF_DEBUG_MODE);
+        mDebugMode = (TwoStatePreference) findPreference(PREF_DEBUG_MODE);
         updateDebugMode();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java
index 2358477..fb1a210 100644
--- a/java/src/com/android/inputmethod/latin/settings/Settings.java
+++ b/java/src/com/android/inputmethod/latin/settings/Settings.java
@@ -39,8 +39,14 @@
 
 public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
     private static final String TAG = Settings.class.getSimpleName();
+    // Settings screens
+    public static final String SCREEN_INPUT = "screen_input";
+    public static final String SCREEN_MULTI_LINGUAL = "screen_multi_lingual";
+    public static final String SCREEN_GESTURE = "screen_gesture";
+    public static final String SCREEN_CORRECTION = "screen_correction";
+    public static final String SCREEN_ADVANCED = "screen_advanced";
+    public static final String SCREEN_DEBUG = "screen_debug";
     // 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";
     public static final String PREF_VIBRATE_ON = "vibrate_on";
     public static final String PREF_SOUND_ON = "sound_on";
@@ -48,13 +54,10 @@
     // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
     public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
     public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
-    public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
     public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
     public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
     public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
     public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
-    public static final String PREF_MISC_SETTINGS = "misc_settings";
-    public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
     public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
     public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts";
     public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
@@ -75,7 +78,6 @@
     public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
             "pref_key_preview_popup_dismiss_delay";
     public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
-    public static final String PREF_GESTURE_SETTINGS = "gesture_typing_settings";
     public static final String PREF_GESTURE_INPUT = "gesture_input";
     public static final String PREF_VIBRATION_DURATION_SETTINGS =
             "pref_vibration_duration_settings";
@@ -89,7 +91,6 @@
 
     public static final String PREF_INPUT_LANGUAGE = "input_language";
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
-    public static final String PREF_DEBUG_SETTINGS = "debug_settings";
     public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
 
     public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging";
@@ -105,8 +106,6 @@
             "pref_last_used_personalization_dict_wiped_time";
     private static final String PREF_CORPUS_HANDLES_FOR_PERSONALIZATION =
             "pref_corpus_handles_for_personalization";
-    public static final String PREF_SEND_FEEDBACK = "send_feedback";
-    public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
 
     // Emoji
     public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
index 5eb0377..9d82412 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java
@@ -27,13 +27,15 @@
 import android.media.AudioManager;
 import android.os.Build;
 import android.os.Bundle;
-import android.preference.CheckBoxPreference;
 import android.preference.ListPreference;
 import android.preference.Preference;
-import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
+import android.preference.TwoStatePreference;
 import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
@@ -61,6 +63,10 @@
             DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
             || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
 
+    private static final int NO_MENU_GROUP = Menu.NONE; // We don't care about menu grouping.
+    private static final int MENU_FEEDBACK = Menu.FIRST; // The first menu item id and order.
+    private static final int MENU_ABOUT = Menu.FIRST + 1; // The second menu item id and order.
+
     private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
         final Preference preference = findPreference(preferenceKey);
         if (preference != null) {
@@ -93,6 +99,7 @@
     @Override
     public void onCreate(final Bundle icicle) {
         super.onCreate(icicle);
+        setHasOptionsMenu(true);
         setInputMethodSettingsCategoryTitle(R.string.language_selection_title);
         setSubtypeEnablerTitle(R.string.select_language);
         addPreferencesFromResource(R.xml.prefs);
@@ -117,66 +124,42 @@
 
         ensureConsistencyOfAutoCorrectionSettings();
 
-        final PreferenceGroup generalSettings =
-                (PreferenceGroup) findPreference(Settings.PREF_GENERAL_SETTINGS);
-        final PreferenceGroup miscSettings =
-                (PreferenceGroup) findPreference(Settings.PREF_MISC_SETTINGS);
-
-        final Preference debugSettings = findPreference(Settings.PREF_DEBUG_SETTINGS);
-        if (debugSettings != null) {
-            if (Settings.isInternal(prefs)) {
-                final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
-                debugSettingsIntent.setClassName(
-                        context.getPackageName(), DebugSettingsActivity.class.getName());
-                debugSettings.setIntent(debugSettingsIntent);
-            } else {
-                miscSettings.removePreference(debugSettings);
-            }
-        }
-
-        final Preference feedbackSettings = findPreference(Settings.PREF_SEND_FEEDBACK);
-        final Preference aboutSettings = findPreference(Settings.PREF_ABOUT_KEYBOARD);
-        if (feedbackSettings != null) {
-            if (FeedbackUtils.isFeedbackFormSupported()) {
-                feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-                    @Override
-                    public boolean onPreferenceClick(final Preference pref) {
-                        FeedbackUtils.showFeedbackForm(getActivity());
-                        return true;
-                    }
-                });
-                aboutSettings.setTitle(FeedbackUtils.getAboutKeyboardTitleResId());
-                aboutSettings.setIntent(FeedbackUtils.getAboutKeyboardIntent(getActivity()));
-            } else {
-                miscSettings.removePreference(feedbackSettings);
-                miscSettings.removePreference(aboutSettings);
-            }
+        final PreferenceScreen inputScreen =
+                (PreferenceScreen) findPreference(Settings.SCREEN_INPUT);
+        final PreferenceScreen advancedScreen =
+                (PreferenceScreen) findPreference(Settings.SCREEN_ADVANCED);
+        final Preference debugScreen = findPreference(Settings.SCREEN_DEBUG);
+        if (Settings.isInternal(prefs)) {
+            final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
+            debugSettingsIntent.setClassName(
+                    context.getPackageName(), DebugSettingsActivity.class.getName());
+            debugScreen.setIntent(debugSettingsIntent);
+        } else {
+            advancedScreen.removePreference(debugScreen);
         }
 
         final boolean showVoiceKeyOption = res.getBoolean(
                 R.bool.config_enable_show_voice_key_option);
         if (!showVoiceKeyOption) {
-            removePreference(Settings.PREF_VOICE_INPUT_KEY, generalSettings);
+            removePreference(Settings.PREF_VOICE_INPUT_KEY, inputScreen);
         }
 
-        final PreferenceGroup advancedSettings =
-                (PreferenceGroup) findPreference(Settings.PREF_ADVANCED_SETTINGS);
         if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) {
-            removePreference(Settings.PREF_VIBRATE_ON, generalSettings);
-            removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings);
+            removePreference(Settings.PREF_VIBRATE_ON, inputScreen);
+            removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedScreen);
         }
         if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
+            final PreferenceScreen multiLingualScreen =
+                    (PreferenceScreen) findPreference(Settings.SCREEN_MULTI_LINGUAL);
+            removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, multiLingualScreen);
             removePreference(
-                    Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, advancedSettings);
-            removePreference(
-                    Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, advancedSettings);
+                    Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, multiLingualScreen);
         }
 
-
         // TODO: consolidate key preview dismiss delay with the key preview animation parameters.
         if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
-            removePreference(Settings.PREF_POPUP_ON, generalSettings);
-            removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings);
+            removePreference(Settings.PREF_POPUP_ON, inputScreen);
+            removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedScreen);
         } else {
             // TODO: Cleanup this setup.
             final ListPreference keyPreviewPopupDismissDelay =
@@ -199,18 +182,18 @@
         }
 
         if (!res.getBoolean(R.bool.config_setup_wizard_available)) {
-            removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings);
+            removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedScreen);
         }
 
-        final PreferenceGroup textCorrectionGroup =
-                (PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS);
+        final PreferenceScreen correctionScreen =
+                (PreferenceScreen) findPreference(Settings.SCREEN_CORRECTION);
         final PreferenceScreen dictionaryLink =
                 (PreferenceScreen) findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
         final Intent intent = dictionaryLink.getIntent();
         intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
         final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
         if (0 >= number) {
-            textCorrectionGroup.removePreference(dictionaryLink);
+            correctionScreen.removePreference(dictionaryLink);
         }
 
         if (ProductionFlag.IS_METRICS_LOGGING_SUPPORTED) {
@@ -224,7 +207,7 @@
                 enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
             }
         } else {
-            removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, textCorrectionGroup);
+            removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, advancedScreen);
         }
 
         final Preference editPersonalDictionary =
@@ -238,7 +221,7 @@
         }
 
         if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
-            removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
+            removePreference(Settings.SCREEN_GESTURE, getPreferenceScreen());
         }
 
         AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
@@ -261,8 +244,8 @@
             voiceInputKeyOption.setSummary(isShortcutImeEnabled ? null
                     : res.getText(R.string.voice_input_disabled_summary));
         }
-        final CheckBoxPreference showSetupWizardIcon =
-                (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
+        final TwoStatePreference showSetupWizardIcon =
+                (TwoStatePreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
         if (showSetupWizardIcon != null) {
             showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
         }
@@ -476,4 +459,29 @@
             userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
         }
     }
+
+    @Override
+    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
+        if (!FeedbackUtils.isFeedbackFormSupported()) {
+            return;
+        }
+        menu.add(NO_MENU_GROUP, MENU_FEEDBACK /* itemId */, MENU_FEEDBACK /* order */,
+                R.string.send_feedback);
+        menu.add(NO_MENU_GROUP, MENU_ABOUT /* itemId */, MENU_ABOUT /* order */,
+                FeedbackUtils.getAboutKeyboardTitleResId());
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(final MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == MENU_FEEDBACK) {
+            FeedbackUtils.showFeedbackForm(getActivity());
+            return true;
+        }
+        if (itemId == MENU_ABOUT) {
+            startActivity(FeedbackUtils.getAboutKeyboardIntent(getActivity()));
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
index 4410401..8de5fed 100644
--- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
+++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java
@@ -125,8 +125,7 @@
         mSlidingKeyInputPreviewEnabled = prefs.getBoolean(
                 DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true);
         mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res)
-                && !mInputAttributes.mIsPasswordField
-                && !mInputAttributes.hasNoMicrophoneKeyOption()
+                && mInputAttributes.mShouldShowVoiceInputKey
                 && SubtypeSwitcher.getInstance().isShortcutImeEnabled();
         final String autoCorrectionThresholdRawValue = prefs.getString(
                 Settings.PREF_AUTO_CORRECTION_THRESHOLD,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 19b48f0..46f5cde 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -44,6 +44,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.PunctuationSuggestions;
 import com.android.inputmethod.latin.R;
@@ -386,6 +387,12 @@
         final float scaleX = getTextScaleX(word, width, wordView.getPaint());
         wordView.setText(text); // TextView.setText() resets text scale x to 1.0.
         wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE));
+        // A <code>wordView</code> should be disabled when <code>word</code> is empty in order to
+        // make it unclickable.
+        // With accessibility touch exploration on, <code>wordView</code> should be enabled even
+        // when it is empty to avoid announcing as "disabled".
+        wordView.setEnabled(!TextUtils.isEmpty(word)
+                || AccessibilityUtils.getInstance().isTouchExplorationEnabled());
         return wordView;
     }
 
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index 47b5c33..72f8f87 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -92,3 +92,6 @@
 
 #################### Unit test on host environment
 include $(LOCAL_PATH)/HostUnitTests.mk
+
+#################### Unit test on target environment
+include $(LOCAL_PATH)/TargetUnitTests.mk
diff --git a/native/jni/TargetUnitTests.mk b/native/jni/TargetUnitTests.mk
new file mode 100644
index 0000000..12aae44
--- /dev/null
+++ b/native/jni/TargetUnitTests.mk
@@ -0,0 +1,55 @@
+# Copyright (C) 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.
+
+LOCAL_PATH := $(call my-dir)
+
+######################################
+include $(CLEAR_VARS)
+
+include $(LOCAL_PATH)/NativeFileList.mk
+
+#################### Target library for unit test
+LATIN_IME_SRC_DIR := src
+LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+LOCAL_MODULE := liblatinime_target_static_for_unittests
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(addprefix $(LATIN_IME_SRC_DIR)/, $(LATIN_IME_CORE_SRC_FILES))
+# Here intentionally use libc++_shared rather than libc++_static because
+# $(BUILD_NATIVE_TEST) has not yet supported libc++_static.
+LOCAL_SDK_VERSION := 14
+LOCAL_NDK_STL_VARIANT := c++_shared
+include $(BUILD_STATIC_LIBRARY)
+
+#################### Target native tests
+include $(CLEAR_VARS)
+LATIN_IME_TEST_SRC_DIR := tests
+LOCAL_CFLAGS += -std=c++11 -Wno-unused-parameter -Wno-unused-function
+LOCAL_CLANG := true
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/$(LATIN_IME_SRC_DIR)
+LOCAL_MODULE := liblatinime_target_unittests
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES :=  \
+    $(addprefix $(LATIN_IME_TEST_SRC_DIR)/, $(LATIN_IME_CORE_TEST_FILES))
+LOCAL_STATIC_LIBRARIES += liblatinime_target_static_for_unittests
+# Here intentionally include external/libcxx/libcxx.mk rather because
+# $(BUILD_NATIVE_TEST) fails when LOCAL_NDK_STL_VARIANT is specified.
+include external/libcxx/libcxx.mk
+include $(BUILD_NATIVE_TEST)
+
+#################### Clean up the tmp vars
+LATIN_IME_SRC_DIR :=
+LATIN_IME_TEST_SRC_DIR :=
+include $(LOCAL_PATH)/CleanupNativeFileList.mk
diff --git a/native/jni/run-tests.sh b/native/jni/run-tests.sh
index 5b60e0d..3da4527 100755
--- a/native/jni/run-tests.sh
+++ b/native/jni/run-tests.sh
@@ -13,17 +13,56 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+function usage() {
+    echo "usage: source run-tests.sh [--host] [--target] [-h] [--help]"  1>&2
+    echo "    --host: run test on the host environment"  1>&2
+    echo "    --no-host: skip host test"  1>&2
+    echo "    --target: run test on the target environment"  1>&2
+    echo "    --no-target: skip target device test"  1>&2
+}
+
+# check script arguments
 if [[ $(type -t mmm) != function ]]; then
-echo "Usage:" 1>&2
-echo "    source $0" 1>&2
-echo "  or" 1>&2
-echo "    . $0" 1>&2
+usage
 if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi
 fi
 
+show_usage=no
+enable_host_test=yes
+enable_target_device_test=no
+while [ "$1" != "" ]
+  do
+  case "$1" in
+    "-h") show_usage=yes;;
+    "--help") show_usage=yes;;
+    "--target") enable_target_device_test=yes;;
+    "--no-target") enable_target_device_test=no;;
+    "--host") enable_host_test=yes;;
+    "--no-host") enable_host_test=no;;
+  esac
+  shift
+done
+
+if [[ $show_usage == yes ]]; then
+  usage
+  if [[ ${BASH_SOURCE[0]} != $0 ]]; then return; else exit 1; fi
+fi
+
+target_test_name=liblatinime_target_unittests
+host_test_name=liblatinime_host_unittests
+
 pushd $PWD > /dev/null
 cd $(gettop)
 mmm -j16 packages/inputmethods/LatinIME/native/jni || \
-    make -j16 liblatinime_host_unittests
-${ANDROID_HOST_OUT}/bin/liblatinime_host_unittests
-popd > /dev/null
\ No newline at end of file
+    make -j16 adb $target_test_name $host_test_name
+if [[ $enable_host_test == yes ]]; then
+  $ANDROID_HOST_OUT/bin/$host_test_name
+fi
+if [[ $enable_target_device_test == yes ]]; then
+  target_test_local=$ANDROID_PRODUCT_OUT/data/nativetest/$target_test_name/$target_test_name
+  target_test_device=/data/nativetest/$target_test_name/$target_test_name
+  adb push $target_test_local $target_test_device
+  adb shell $target_test_device
+  adb shell rm -rf $target_test_device
+fi
+popd > /dev/null
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_reader.h
index 4032a67..1999a51 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_node_reader.h
@@ -58,7 +58,7 @@
 
     ~Ver4PatriciaTrieNodeReader() {}
 
-    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const {
+    virtual const PtNodeParams fetchPtNodeParamsInBufferFromPtNodePos(const int ptNodePos) const {
         return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos,
                 NOT_A_DICT_POS /* siblingNodePos */);
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
index 805820b..4ac0f40 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_policy.cpp
@@ -135,7 +135,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
@@ -146,7 +146,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
@@ -158,7 +158,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
@@ -410,7 +410,7 @@
         AKLOGE("getWordProperty is called for invalid word.");
         return WordProperty();
     }
-    const PtNodeParams ptNodeParams = mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
     std::vector<int> codePointVector(ptNodeParams.getCodePoints(),
             ptNodeParams.getCodePoints() + ptNodeParams.getCodePointCount());
     const ProbabilityEntry probabilityEntry =
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_writing_helper.cpp
index 99eed0f..3fb4caa 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/backward/v402/ver4_patricia_trie_writing_helper.cpp
@@ -224,7 +224,7 @@
         const int ptNodePos = priorityQueue.top().getDictPos();
         priorityQueue.pop();
         const PtNodeParams ptNodeParams =
-                ptNodeReader->fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+                ptNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
         if (ptNodeParams.representsNonWordInfo()) {
             continue;
         }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.cpp
index 1f00fc6..db1a802 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_gc_event_listeners.cpp
@@ -65,7 +65,7 @@
 
 bool DynamicPtGcEventListeners::TraversePolicyToUpdateBigramProbability
         ::onVisitingPtNode(const PtNodeParams *const ptNodeParams) {
-    if (!ptNodeParams->isDeleted() && ptNodeParams->hasBigrams()) {
+    if (!ptNodeParams->isDeleted()) {
         int bigramEntryCount = 0;
         if (!mPtNodeWriter->updateAllBigramEntriesAndDeleteUselessEntries(ptNodeParams,
                 &bigramEntryCount)) {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
index cc7b5ff..2e05bf3 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_reading_helper.h
@@ -126,7 +126,7 @@
         if (isEnd()) {
             return PtNodeParams();
         }
-        return mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(mReadingState.mPos);
+        return mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(mReadingState.mPos);
     }
 
     AK_FORCE_INLINE bool isValidTerminalNode(const PtNodeParams &ptNodeParams) const {
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
index 9e57585..f31c914 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/dynamic_pt_updating_helper.cpp
@@ -87,9 +87,9 @@
 bool DynamicPtUpdatingHelper::addBigramWords(const int word0Pos, const int word1Pos,
         const BigramProperty *const bigramProperty, bool *const outAddedNewBigram) {
     const PtNodeParams sourcePtNodeParams(
-            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word0Pos));
+            mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(word0Pos));
     const PtNodeParams targetPtNodeParams(
-            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word1Pos));
+            mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(word1Pos));
     return mPtNodeWriter->addNewBigramEntry(&sourcePtNodeParams, &targetPtNodeParams,
             bigramProperty, outAddedNewBigram);
 }
@@ -97,16 +97,16 @@
 // Remove a bigram relation from word0Pos to word1Pos.
 bool DynamicPtUpdatingHelper::removeBigramWords(const int word0Pos, const int word1Pos) {
     const PtNodeParams sourcePtNodeParams(
-            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word0Pos));
+            mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(word0Pos));
     const PtNodeParams targetPtNodeParams(
-            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(word1Pos));
+            mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(word1Pos));
     return mPtNodeWriter->removeBigramEntry(&sourcePtNodeParams, &targetPtNodeParams);
 }
 
 bool DynamicPtUpdatingHelper::addShortcutTarget(const int wordPos,
         const int *const targetCodePoints, const int targetCodePointCount,
         const int shortcutProbability) {
-    const PtNodeParams ptNodeParams(mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(wordPos));
+    const PtNodeParams ptNodeParams(mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(wordPos));
     return mPtNodeWriter->addShortcutTarget(&ptNodeParams, targetCodePoints, targetCodePointCount,
             shortcutProbability);
 }
@@ -125,7 +125,7 @@
 
 bool DynamicPtUpdatingHelper::setPtNodeProbability(const PtNodeParams *const originalPtNodeParams,
         const UnigramProperty *const unigramProperty, bool *const outAddedNewUnigram) {
-    if (originalPtNodeParams->isTerminal()) {
+    if (originalPtNodeParams->isTerminal() && !originalPtNodeParams->isDeleted()) {
         // Overwrites the probability.
         *outAddedNewUnigram = false;
         return mPtNodeWriter->updatePtNodeUnigramProperty(originalPtNodeParams, unigramProperty);
@@ -260,7 +260,7 @@
     }
     // Load node info. Information of the 1st part will be fetched.
     const PtNodeParams ptNodeParams(
-            mPtNodeReader->fetchNodeInfoInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos));
+            mPtNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(firstPartOfReallocatedPtNodePos));
     // Update children position.
     return mPtNodeWriter->updateChildrenPosition(&ptNodeParams, actualChildrenPos);
 }
@@ -270,8 +270,8 @@
         const bool isNotAWord, const bool isBlacklisted, const bool isTerminal, const int parentPos,
         const int codePointCount, const int *const codePoints, const int probability) const {
     const PatriciaTrieReadingUtils::NodeFlags flags = PatriciaTrieReadingUtils::createAndGetFlags(
-            isBlacklisted, isNotAWord, isTerminal, originalPtNodeParams->hasShortcutTargets(),
-            originalPtNodeParams->hasBigrams(), codePointCount > 1 /* hasMultipleChars */,
+            isBlacklisted, isNotAWord, isTerminal, false /* hasShortcutTargets */,
+            false /* hasBigrams */, codePointCount > 1 /* hasMultipleChars */,
             CHILDREN_POSITION_FIELD_SIZE);
     return PtNodeParams(originalPtNodeParams, flags, parentPos, codePointCount, codePoints,
             probability);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h
index c6b2a8b..31299a7 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/pt_common/pt_node_reader.h
@@ -27,7 +27,8 @@
 class PtNodeReader {
  public:
     virtual ~PtNodeReader() {}
-    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const = 0;
+    virtual const PtNodeParams fetchPtNodeParamsInBufferFromPtNodePos(
+            const int ptNodePos) const = 0;
 
  protected:
     PtNodeReader() {};
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
index a6a470c..7e1f3b2 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/patricia_trie_policy.cpp
@@ -282,7 +282,8 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    const PtNodeParams ptNodeParams = mPtNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    const PtNodeParams ptNodeParams =
+            mPtNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
     if (ptNodeParams.isNotAWord() || ptNodeParams.isBlacklisted()) {
         // If this is not a word, or if it's a blacklisted entry, it should behave as
         // having no probability outside of the suggestion process (where it should be used
@@ -296,14 +297,14 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    return mPtNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos).getShortcutPos();
+    return mPtNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos).getShortcutPos();
 }
 
 int PatriciaTriePolicy::getBigramsPositionOfPtNode(const int ptNodePos) const {
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    return mPtNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos).getBigramsPos();
+    return mPtNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos).getBigramsPos();
 }
 
 int PatriciaTriePolicy::createAndGetLeavingChildNode(const DicNode *const dicNode,
@@ -339,7 +340,8 @@
         AKLOGE("getWordProperty was called for invalid word.");
         return WordProperty();
     }
-    const PtNodeParams ptNodeParams = mPtNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    const PtNodeParams ptNodeParams =
+            mPtNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
     std::vector<int> codePointVector(ptNodeParams.getCodePoints(),
             ptNodeParams.getCodePoints() + ptNodeParams.getCodePointCount());
     // Fetch bigram information.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
index 0c8de0d..c1e9387 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.cpp
@@ -20,7 +20,7 @@
 
 namespace latinime {
 
-const PtNodeParams Ver2ParticiaTrieNodeReader::fetchNodeInfoInBufferFromPtNodePos(
+const PtNodeParams Ver2ParticiaTrieNodeReader::fetchPtNodeParamsInBufferFromPtNodePos(
         const int ptNodePos) const {
     if (ptNodePos < 0 || ptNodePos >= mDictSize) {
         // Reading invalid position because of bug or broken dictionary.
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
index 86fc89c..f0725b6 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v2/ver2_patricia_trie_node_reader.h
@@ -36,7 +36,7 @@
             : mDictBuffer(dictBuffer), mDictSize(dictSize), mBigramPolicy(bigramPolicy),
               mShortuctPolicy(shortcutPolicy) {}
 
-    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const;
+    virtual const PtNodeParams fetchPtNodeParamsInBufferFromPtNodePos(const int ptNodePos) const;
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(Ver2ParticiaTrieNodeReader);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h
index f24307e..22ed4a6 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_reader.h
@@ -41,7 +41,7 @@
 
     ~Ver4PatriciaTrieNodeReader() {}
 
-    virtual const PtNodeParams fetchNodeInfoInBufferFromPtNodePos(const int ptNodePos) const {
+    virtual const PtNodeParams fetchPtNodeParamsInBufferFromPtNodePos(const int ptNodePos) const {
         return fetchPtNodeInfoFromBufferAndProcessMovedPtNode(ptNodePos,
                 NOT_A_DICT_POS /* siblingNodePos */);
     }
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
index f89d3d7..3d8da91 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.cpp
@@ -231,14 +231,6 @@
                 sourcePtNodeParams->getTerminalId(), targetPtNodeParam->getTerminalId());
         return false;
     }
-    if (!sourcePtNodeParams->hasBigrams()) {
-        // Update has bigrams flag.
-        return updatePtNodeFlags(sourcePtNodeParams->getHeadPos(),
-                sourcePtNodeParams->isBlacklisted(), sourcePtNodeParams->isNotAWord(),
-                sourcePtNodeParams->isTerminal(), sourcePtNodeParams->hasShortcutTargets(),
-                true /* hasBigrams */,
-                sourcePtNodeParams->getCodePointCount() > 1 /* hasMultipleChars */);
-    }
     return true;
 }
 
@@ -303,28 +295,9 @@
         AKLOGE("Cannot add new shortuct entry. terminalId: %d", ptNodeParams->getTerminalId());
         return false;
     }
-    if (!ptNodeParams->hasShortcutTargets()) {
-        // Update has shortcut targets flag.
-        return updatePtNodeFlags(ptNodeParams->getHeadPos(),
-                ptNodeParams->isBlacklisted(), ptNodeParams->isNotAWord(),
-                ptNodeParams->isTerminal(), true /* hasShortcutTargets */,
-                ptNodeParams->hasBigrams(),
-                ptNodeParams->getCodePointCount() > 1 /* hasMultipleChars */);
-    }
     return true;
 }
 
-bool Ver4PatriciaTrieNodeWriter::updatePtNodeHasBigramsAndShortcutTargetsFlags(
-        const PtNodeParams *const ptNodeParams) {
-    const bool hasBigrams = mBuffers->getBigramDictContent()->getBigramListHeadPos(
-            ptNodeParams->getTerminalId()) != NOT_A_DICT_POS;
-    const bool hasShortcutTargets = mBuffers->getShortcutDictContent()->getShortcutListHeadPos(
-            ptNodeParams->getTerminalId()) != NOT_A_DICT_POS;
-    return updatePtNodeFlags(ptNodeParams->getHeadPos(), ptNodeParams->isBlacklisted(),
-            ptNodeParams->isNotAWord(), ptNodeParams->isTerminal(), hasShortcutTargets,
-            hasBigrams, ptNodeParams->getCodePointCount() > 1 /* hasMultipleChars */);
-}
-
 bool Ver4PatriciaTrieNodeWriter::writePtNodeAndGetTerminalIdAndAdvancePosition(
         const PtNodeParams *const ptNodeParams, int *const outTerminalId,
         int *const ptNodeWritingPos) {
@@ -377,8 +350,7 @@
         return false;
     }
     return updatePtNodeFlags(nodePos, ptNodeParams->isBlacklisted(), ptNodeParams->isNotAWord(),
-            isTerminal, ptNodeParams->hasShortcutTargets(), ptNodeParams->hasBigrams(),
-            ptNodeParams->getCodePointCount() > 1 /* hasMultipleChars */);
+            isTerminal, ptNodeParams->getCodePointCount() > 1 /* hasMultipleChars */);
 }
 
 const ProbabilityEntry Ver4PatriciaTrieNodeWriter::createUpdatedEntryFrom(
@@ -402,11 +374,11 @@
 
 bool Ver4PatriciaTrieNodeWriter::updatePtNodeFlags(const int ptNodePos,
         const bool isBlacklisted, const bool isNotAWord, const bool isTerminal,
-        const bool hasShortcutTargets, const bool hasBigrams, const bool hasMultipleChars) {
+        const bool hasMultipleChars) {
     // Create node flags and write them.
     PatriciaTrieReadingUtils::NodeFlags nodeFlags =
             PatriciaTrieReadingUtils::createAndGetFlags(isBlacklisted, isNotAWord, isTerminal,
-                    hasShortcutTargets, hasBigrams, hasMultipleChars,
+                    false /* hasShortcutTargets */, false /* hasBigrams */, hasMultipleChars,
                     CHILDREN_POSITION_FIELD_SIZE);
     if (!DynamicPtWritingUtils::writeFlags(mTrieBuffer, nodeFlags, ptNodePos)) {
         AKLOGE("Cannot write PtNode flags. flags: %x, pos: %d", nodeFlags, ptNodePos);
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
index e90bc44..162dc9b 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_node_writer.h
@@ -93,8 +93,6 @@
             const int *const targetCodePoints, const int targetCodePointCount,
             const int shortcutProbability);
 
-    bool updatePtNodeHasBigramsAndShortcutTargetsFlags(const PtNodeParams *const ptNodeParams);
-
  private:
     DISALLOW_COPY_AND_ASSIGN(Ver4PatriciaTrieNodeWriter);
 
@@ -110,8 +108,7 @@
             const UnigramProperty *const unigramProperty) const;
 
     bool updatePtNodeFlags(const int ptNodePos, const bool isBlacklisted, const bool isNotAWord,
-            const bool isTerminal, const bool hasShortcutTargets, const bool hasBigrams,
-            const bool hasMultipleChars);
+            const bool isTerminal, const bool hasMultipleChars);
 
     static const int CHILDREN_POSITION_FIELD_SIZE;
 
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
index aec3b8e..f7f2a32 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_policy.cpp
@@ -125,7 +125,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_PROBABILITY;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted() || ptNodeParams.isBlacklisted() || ptNodeParams.isNotAWord()) {
         return NOT_A_PROBABILITY;
     }
@@ -136,7 +136,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
@@ -148,7 +148,7 @@
     if (ptNodePos == NOT_A_DICT_POS) {
         return NOT_A_DICT_POS;
     }
-    const PtNodeParams ptNodeParams(mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos));
+    const PtNodeParams ptNodeParams(mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos));
     if (ptNodeParams.isDeleted()) {
         return NOT_A_DICT_POS;
     }
@@ -222,8 +222,24 @@
 }
 
 bool Ver4PatriciaTriePolicy::removeUnigramEntry(const int *const word, const int length) {
-    // TODO: Implement.
-    return false;
+    if (!mBuffers->isUpdatable()) {
+        AKLOGI("Warning: removeUnigramEntry() is called for non-updatable dictionary.");
+        return false;
+    }
+    const int ptNodePos = getTerminalPtNodePositionOfWord(word, length,
+            false /* forceLowerCaseSearch */);
+    if (ptNodePos == NOT_A_DICT_POS) {
+        return false;
+    }
+    const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
+    if (!mNodeWriter.markPtNodeAsDeleted(&ptNodeParams)) {
+        AKLOGE("Cannot remove unigram. ptNodePos: %d", ptNodePos);
+        return false;
+    }
+    if (!ptNodeParams.representsNonWordInfo()) {
+        mUnigramCount--;
+    }
+    return true;
 }
 
 bool Ver4PatriciaTriePolicy::addNgramEntry(const PrevWordsInfo *const prevWordsInfo,
@@ -405,7 +421,7 @@
         AKLOGE("getWordProperty is called for invalid word.");
         return WordProperty();
     }
-    const PtNodeParams ptNodeParams = mNodeReader.fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+    const PtNodeParams ptNodeParams = mNodeReader.fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
     std::vector<int> codePointVector(ptNodeParams.getCodePoints(),
             ptNodeParams.getCodePoints() + ptNodeParams.getCodePointCount());
     const ProbabilityEntry probabilityEntry =
diff --git a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
index e868ddf..40fdfa0 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/structure/v4/ver4_patricia_trie_writing_helper.cpp
@@ -215,7 +215,7 @@
         const int ptNodePos = priorityQueue.top().getDictPos();
         priorityQueue.pop();
         const PtNodeParams ptNodeParams =
-                ptNodeReader->fetchNodeInfoInBufferFromPtNodePos(ptNodePos);
+                ptNodeReader->fetchPtNodeParamsInBufferFromPtNodePos(ptNodePos);
         if (ptNodeParams.representsNonWordInfo()) {
             continue;
         }
@@ -286,8 +286,9 @@
     }
     if (!mPtNodeWriter->updateTerminalId(ptNodeParams, it->second)) {
         AKLOGE("Cannot update terminal id. %d -> %d", it->first, it->second);
+        return false;
     }
-    return mPtNodeWriter->updatePtNodeHasBigramsAndShortcutTargetsFlags(ptNodeParams);
+    return true;
 }
 
 } // namespace latinime
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
index 2cbc041..3ef03f4 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtils.java
@@ -26,11 +26,13 @@
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
 import java.util.ArrayList;
 
@@ -51,14 +53,17 @@
     public final static String ENCRYPTION = "encrypted";
 
     private final static int MAX_DECODE_DEPTH = 8;
+    private final static int COPY_BUFFER_SIZE = 8192;
 
     public static class DecoderChainSpec {
         ArrayList<String> mDecoderSpec = new ArrayList<>();
         File mFile;
+
         public DecoderChainSpec addStep(final String stepDescription) {
             mDecoderSpec.add(stepDescription);
             return this;
         }
+
         public String describeChain() {
             final StringBuilder s = new StringBuilder("raw");
             for (final String step : mDecoderSpec) {
@@ -70,13 +75,10 @@
     }
 
     public static void copy(final InputStream input, final OutputStream output) throws IOException {
-        final byte[] buffer = new byte[1000];
-        final BufferedInputStream in = new BufferedInputStream(input);
-        final BufferedOutputStream out = new BufferedOutputStream(output);
-        for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer))
+        final byte[] buffer = new byte[COPY_BUFFER_SIZE];
+        for (int readBytes = input.read(buffer); readBytes >= 0; readBytes = input.read(buffer)) {
             output.write(buffer, 0, readBytes);
-        in.close();
-        out.close();
+        }
     }
 
     /**
@@ -131,11 +133,15 @@
         try {
             final File dst = File.createTempFile(PREFIX, SUFFIX);
             dst.deleteOnExit();
-            final FileOutputStream dstStream = new FileOutputStream(dst);
-            copy(Compress.getUncompressedStream(new BufferedInputStream(new FileInputStream(src))),
-                    new BufferedOutputStream(dstStream)); // #copy() closes the streams
-            return dst;
-        } catch (IOException e) {
+            try (
+                final InputStream input = Compress.getUncompressedStream(
+                        new BufferedInputStream(new FileInputStream(src)));
+                final OutputStream output = new BufferedOutputStream(new FileOutputStream(dst))
+            ) {
+                copy(input, output);
+                return dst;
+            }
+        } catch (final IOException e) {
             // Could not uncompress the file: presumably the file is simply not a compressed file
             return null;
         }
@@ -150,20 +156,20 @@
         try {
             final File dst = File.createTempFile(PREFIX, SUFFIX);
             dst.deleteOnExit();
-            final FileOutputStream dstStream = new FileOutputStream(dst);
-            copy(Crypt.getDecryptedStream(new BufferedInputStream(new FileInputStream(src))),
-                    dstStream); // #copy() closes the streams
-            return dst;
-        } catch (IOException e) {
+            try (
+                final InputStream input = Crypt.getDecryptedStream(
+                        new BufferedInputStream(new FileInputStream(src)));
+                final OutputStream output = new BufferedOutputStream(new FileOutputStream(dst))
+            ) {
+                copy(input, output);
+                return dst;
+            }
+        } catch (final IOException e) {
             // Could not decrypt the file: presumably the file is simply not a crypted file
             return null;
         }
     }
 
-    static void crash(final String filename, final Exception e) {
-        throw new RuntimeException("Can't read file " + filename, e);
-    }
-
     static FusionDictionary getDictionary(final String filename, final boolean report) {
         final File file = new File(filename);
         if (report) {
@@ -172,45 +178,40 @@
         }
         try {
             if (XmlDictInputOutput.isXmlUnigramDictionary(filename)) {
-                if (report) System.out.println("Format : XML unigram list");
+                if (report) {
+                    System.out.println("Format : XML unigram list");
+                }
                 return XmlDictInputOutput.readDictionaryXml(
                         new BufferedInputStream(new FileInputStream(file)),
                         null /* shortcuts */, null /* bigrams */);
-            } else {
-                final DecoderChainSpec decodedSpec = getRawDictionaryOrNull(file);
-                if (null == decodedSpec) {
-                    crash(filename, new RuntimeException(
-                            filename + " does not seem to be a dictionary file"));
-                } else if (CombinedInputOutput.isCombinedDictionary(
-                        decodedSpec.mFile.getAbsolutePath())){
-                    if (report) {
-                        System.out.println("Format : Combined format");
-                        System.out.println("Packaging : " + decodedSpec.describeChain());
-                        System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
-                    }
-                    return CombinedInputOutput.readDictionaryCombined(
-                            new BufferedInputStream(new FileInputStream(decodedSpec.mFile)));
-                } else {
-                    final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(
-                            decodedSpec.mFile, 0, decodedSpec.mFile.length(),
-                            DictDecoder.USE_BYTEARRAY);
-                    if (report) {
-                        System.out.println("Format : Binary dictionary format");
-                        System.out.println("Packaging : " + decodedSpec.describeChain());
-                        System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
-                    }
-                    return dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */);
+            }
+            final DecoderChainSpec decodedSpec = getRawDictionaryOrNull(file);
+            if (null == decodedSpec) {
+                throw new RuntimeException("Does not seem to be a dictionary file " + filename);
+            }
+            if (CombinedInputOutput.isCombinedDictionary(decodedSpec.mFile.getAbsolutePath())) {
+                if (report) {
+                    System.out.println("Format : Combined format");
+                    System.out.println("Packaging : " + decodedSpec.describeChain());
+                    System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
+                }
+                try (final BufferedReader reader = new BufferedReader(
+                        new InputStreamReader(new FileInputStream(decodedSpec.mFile), "UTF-8"))) {
+                    return CombinedInputOutput.readDictionaryCombined(reader);
                 }
             }
-        } catch (IOException e) {
-            crash(filename, e);
-        } catch (SAXException e) {
-            crash(filename, e);
-        } catch (ParserConfigurationException e) {
-            crash(filename, e);
-        } catch (UnsupportedFormatException e) {
-            crash(filename, e);
+            final DictDecoder dictDecoder = BinaryDictIOUtils.getDictDecoder(
+                    decodedSpec.mFile, 0, decodedSpec.mFile.length(),
+                    DictDecoder.USE_BYTEARRAY);
+            if (report) {
+                System.out.println("Format : Binary dictionary format");
+                System.out.println("Packaging : " + decodedSpec.describeChain());
+                System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
+            }
+            return dictDecoder.readDictionaryBinary(false /* deleteDictIfBroken */);
+        } catch (final IOException | SAXException | ParserConfigurationException |
+                UnsupportedFormatException e) {
+            throw new RuntimeException("Can't read file " + filename, e);
         }
-        return null;
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
index 6a0e1b7..23cbee8 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/CombinedInputOutput.java
@@ -26,13 +26,9 @@
 import com.android.inputmethod.latin.utils.CombinedFormatUtils;
 
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
+import java.io.BufferedWriter;
 import java.io.FileReader;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Writer;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.TreeSet;
@@ -57,27 +53,15 @@
      * @return true if the file is in the combined format, false otherwise
      */
     public static boolean isCombinedDictionary(final String filename) {
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new FileReader(new File(filename)));
+        try (final BufferedReader reader = new BufferedReader(new FileReader(filename))) {
             String firstLine = reader.readLine();
             while (firstLine.startsWith(COMMENT_LINE_STARTER)) {
                 firstLine = reader.readLine();
             }
             return firstLine.matches(
                     "^" + CombinedFormatUtils.DICTIONARY_TAG + "=[^:]+(:[^=]+=[^:]+)*");
-        } catch (FileNotFoundException e) {
+        } catch (final IOException e) {
             return false;
-        } catch (IOException e) {
-            return false;
-        } finally {
-            if (reader != null) {
-                try {
-                    reader.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
     }
 
@@ -87,12 +71,11 @@
      * This is the public method that will read a combined file and return the corresponding memory
      * representation.
      *
-     * @param source the file to read the data from.
+     * @param reader the buffered reader to read the data from.
      * @return the in-memory representation of the dictionary.
      */
-    public static FusionDictionary readDictionaryCombined(final InputStream source)
+    public static FusionDictionary readDictionaryCombined(final BufferedReader reader)
             throws IOException {
-        final BufferedReader reader = new BufferedReader(new InputStreamReader(source, "UTF-8"));
         String headerLine = reader.readLine();
         while (headerLine.startsWith(COMMENT_LINE_STARTER)) {
             headerLine = reader.readLine();
@@ -218,11 +201,11 @@
     /**
      * Writes a dictionary to a combined file.
      *
-     * @param destination a destination stream to write to.
+     * @param destination a destination writer.
      * @param dict the dictionary to write.
      */
-    public static void writeDictionaryCombined(
-            final Writer destination, final FusionDictionary dict) throws IOException {
+    public static void writeDictionaryCombined(final BufferedWriter destination,
+            final FusionDictionary dict) throws IOException {
         final TreeSet<WordProperty> wordPropertiesInDict = new TreeSet<>();
         for (final WordProperty wordProperty : dict) {
             // This for ordering by frequency, then by asciibetic order
@@ -232,6 +215,5 @@
         for (final WordProperty wordProperty : wordPropertiesInDict) {
             destination.write(CombinedFormatUtils.formatWordProperty(wordProperty));
         }
-        destination.close();
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java
index b7f48b5..728a159 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Compress.java
@@ -16,11 +16,6 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -32,8 +27,7 @@
         // This container class is not publicly instantiable.
     }
 
-    public static OutputStream getCompressedStream(final OutputStream out)
-        throws java.io.IOException {
+    public static OutputStream getCompressedStream(final OutputStream out) throws IOException {
         return new GZIPOutputStream(out);
     }
 
@@ -43,7 +37,6 @@
 
     static public class Compressor extends Dicttool.Command {
         public static final String COMMAND = "compress";
-        public static final String STDIN_OR_STDOUT = "-";
 
         public Compressor() {
         }
@@ -61,17 +54,18 @@
             }
             final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
             final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
-            final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
-                    : new BufferedInputStream(new FileInputStream(new File(inFilename)));
-            final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
-                    : new BufferedOutputStream(new FileOutputStream(new File(outFilename)));
-            BinaryDictOffdeviceUtils.copy(input, new GZIPOutputStream(output));
+            try (
+                final InputStream input = getFileInputStreamOrStdIn(inFilename);
+                final OutputStream compressedOutput = getCompressedStream(
+                        getFileOutputStreamOrStdOut(outFilename))
+            ) {
+                BinaryDictOffdeviceUtils.copy(input, compressedOutput);
+            }
         }
     }
 
     static public class Uncompressor extends Dicttool.Command {
         public static final String COMMAND = "uncompress";
-        public static final String STDIN_OR_STDOUT = "-";
 
         public Uncompressor() {
         }
@@ -89,11 +83,13 @@
             }
             final String inFilename = mArgs.length >= 1 ? mArgs[0] : STDIN_OR_STDOUT;
             final String outFilename = mArgs.length >= 2 ? mArgs[1] : STDIN_OR_STDOUT;
-            final InputStream input = inFilename.equals(STDIN_OR_STDOUT) ? System.in
-                    : new BufferedInputStream(new FileInputStream(new File(inFilename)));
-            final OutputStream output = outFilename.equals(STDIN_OR_STDOUT) ? System.out
-                    : new BufferedOutputStream(new FileOutputStream(new File(outFilename)));
-            BinaryDictOffdeviceUtils.copy(new GZIPInputStream(input), output);
+            try (
+                final InputStream uncompressedInput = getUncompressedStream(
+                        getFileInputStreamOrStdIn(inFilename));
+                final OutputStream output = getFileOutputStreamOrStdOut(outFilename)
+            ) {
+                BinaryDictOffdeviceUtils.copy(uncompressedInput, output);
+            }
         }
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 37c8d41..3d0557b 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -27,19 +27,23 @@
 import com.android.inputmethod.latin.makedict.Ver2DictEncoder;
 import com.android.inputmethod.latin.makedict.Ver4DictEncoder;
 
+import org.xml.sax.SAXException;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedReader;
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.util.Arrays;
 import java.util.LinkedList;
 
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.xml.sax.SAXException;
-
 /**
  * Main class/method for DictionaryMaker.
  */
@@ -279,22 +283,21 @@
      */
     private static FusionDictionary readCombinedFile(final String combinedFilename)
         throws FileNotFoundException, IOException {
-        FileInputStream inStream = null;
-        try {
-            final File file = new File(combinedFilename);
-            inStream = new FileInputStream(file);
-            return CombinedInputOutput.readDictionaryCombined(inStream);
-        } finally {
-            if (null != inStream) {
-                try {
-                    inStream.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
+        try (final BufferedReader reader = new BufferedReader(new InputStreamReader(
+                new FileInputStream(combinedFilename), "UTF-8"))
+        ) {
+            return CombinedInputOutput.readDictionaryCombined(reader);
         }
     }
 
+    private static BufferedInputStream getBufferedFileInputStream(final String filename)
+            throws FileNotFoundException {
+        if (filename == null) {
+            return null;
+        }
+        return new BufferedInputStream(new FileInputStream(filename));
+    }
+
     /**
      * Read a dictionary from a unigram XML file, and optionally a bigram XML file.
      *
@@ -310,12 +313,13 @@
     private static FusionDictionary readXmlFile(final String unigramXmlFilename,
             final String shortcutXmlFilename, final String bigramXmlFilename)
             throws FileNotFoundException, SAXException, IOException, ParserConfigurationException {
-        final FileInputStream unigrams = new FileInputStream(new File(unigramXmlFilename));
-        final FileInputStream shortcuts = null == shortcutXmlFilename ? null :
-                new FileInputStream(new File(shortcutXmlFilename));
-        final FileInputStream bigrams = null == bigramXmlFilename ? null :
-                new FileInputStream(new File(bigramXmlFilename));
-        return XmlDictInputOutput.readDictionaryXml(unigrams, shortcuts, bigrams);
+        try (
+            final BufferedInputStream unigrams = getBufferedFileInputStream(unigramXmlFilename);
+            final BufferedInputStream shortcuts = getBufferedFileInputStream(shortcutXmlFilename);
+            final BufferedInputStream bigrams = getBufferedFileInputStream(bigramXmlFilename);
+        ) {
+            return XmlDictInputOutput.readDictionaryXml(unigrams, shortcuts, bigrams);
+        }
     }
 
     /**
@@ -374,8 +378,9 @@
      */
     private static void writeXmlDictionary(final String outputFilename,
             final FusionDictionary dict) throws FileNotFoundException, IOException {
-        XmlDictInputOutput.writeDictionaryXml(new BufferedWriter(new FileWriter(outputFilename)),
-                dict);
+        try (final BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilename))) {
+            XmlDictInputOutput.writeDictionaryXml(writer, dict);
+        }
     }
 
     /**
@@ -388,7 +393,8 @@
      */
     private static void writeCombinedDictionary(final String outputFilename,
             final FusionDictionary dict) throws FileNotFoundException, IOException {
-        CombinedInputOutput.writeDictionaryCombined(
-                new BufferedWriter(new FileWriter(outputFilename)), dict);
+        try (final BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilename))) {
+            CombinedInputOutput.writeDictionaryCombined(writer, dict);
+        }
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
index 8ae035f..e49b350 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
@@ -16,23 +16,63 @@
 
 package com.android.inputmethod.latin.dicttool;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.HashMap;
 
 public class Dicttool {
 
     public static abstract class Command {
+        public static final String STDIN_OR_STDOUT = "-";
         protected String[] mArgs;
+
         public void setArgs(String[] args) throws IllegalArgumentException {
             mArgs = args;
         }
+
+        protected static InputStream getFileInputStreamOrStdIn(final String inFilename)
+                throws FileNotFoundException {
+            if (STDIN_OR_STDOUT.equals(inFilename)) {
+                return System.in;
+            }
+            return getFileInputStream(new File(inFilename));
+        }
+
+        protected static InputStream getFileInputStream(final File inFile)
+                throws FileNotFoundException {
+            return new BufferedInputStream(new FileInputStream(inFile));
+        }
+
+        protected static OutputStream getFileOutputStreamOrStdOut(final String outFilename)
+                throws FileNotFoundException {
+            if (STDIN_OR_STDOUT.equals(outFilename)) {
+                return System.out;
+            }
+            return getFileOutputStream(new File(outFilename));
+        }
+
+        protected static OutputStream getFileOutputStream(final File outFile)
+                throws FileNotFoundException {
+            return new BufferedOutputStream(new FileOutputStream(outFile));
+        }
+
         abstract public String getHelp();
         abstract public void run() throws Exception;
     }
+
     static HashMap<String, Class<? extends Command>> sCommands = new HashMap<>();
+
     static {
         CommandList.populate();
     }
+
     public static void addCommand(final String commandName, final Class<? extends Command> cls) {
         sCommands.put(commandName, cls);
     }
@@ -60,7 +100,7 @@
         return sCommands.containsKey(commandName);
     }
 
-    private Command getCommand(final String[] arguments) {
+    private static Command getCommand(final String[] arguments) {
         final String commandName = arguments[0];
         if (!isCommand(commandName)) {
             throw new RuntimeException("Unknown command : " + commandName);
@@ -76,7 +116,7 @@
      * @param arguments the arguments passed to dicttool.
      * @return 0 for success, an error code otherwise (always 1 at the moment)
      */
-    private int execute(final String[] arguments) {
+    private static int execute(final String[] arguments) {
         final Command command = getCommand(arguments);
         try {
             command.run();
@@ -95,6 +135,6 @@
             return;
         }
         // Exit with the success/error code from #execute() as status.
-        System.exit(new Dicttool().execute(arguments));
+        System.exit(execute(arguments));
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
index dff3387..1f67982 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Package.java
@@ -21,8 +21,9 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
 
 public class Package {
     private Package() {
@@ -86,9 +87,13 @@
             }
             System.out.println("Packaging : " + decodedSpec.describeChain());
             System.out.println("Uncompressed size : " + decodedSpec.mFile.length());
-            final FileOutputStream dstStream = new FileOutputStream(new File(mArgs[1]));
-            BinaryDictOffdeviceUtils.copy(new BufferedInputStream(
-                    new FileInputStream(decodedSpec.mFile)), new BufferedOutputStream(dstStream));
+            try (
+                final InputStream input = getFileInputStream(decodedSpec.mFile);
+                final OutputStream output = new BufferedOutputStream(
+                        getFileOutputStreamOrStdOut(mArgs[1]))
+            ) {
+                BinaryDictOffdeviceUtils.copy(input, output);
+            }
         }
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 7435fa7..bdec447 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -23,13 +23,16 @@
 import com.android.inputmethod.latin.makedict.WeightedString;
 import com.android.inputmethod.latin.makedict.WordProperty;
 
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.BufferedInputStream;
 import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
+import java.io.BufferedWriter;
+import java.io.FileInputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
+import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.TreeSet;
@@ -38,10 +41,6 @@
 import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.DefaultHandler;
-
 /**
  * Reads and writes XML files for a FusionDictionary.
  *
@@ -57,8 +56,6 @@
     private static final String WORD_ATTR = "word";
     private static final String NOT_A_WORD_ATTR = "not_a_word";
 
-    private static final String OPTIONS_KEY = "options";
-
     /**
      * SAX handler for a unigram XML file.
      */
@@ -120,7 +117,6 @@
                     final String attrName = attrs.getLocalName(attrIndex);
                     attributes.put(attrName, attrs.getValue(attrIndex));
                 }
-                final String optionsString = attributes.get(OPTIONS_KEY);
                 mDictionary = new FusionDictionary(new PtNodeArray(),
                         new DictionaryOptions(attributes));
             } else {
@@ -244,14 +240,13 @@
         protected int getValueFromFreqString(final String freqString) {
             if (WHITELIST_MARKER.equals(freqString)) {
                 return WHITELIST_FREQ_VALUE;
-            } else {
-                final int intValue = super.getValueFromFreqString(freqString);
-                if (intValue < MIN_FREQ || intValue > MAX_FREQ) {
-                    throw new RuntimeException("Shortcut freq out of range. Accepted range is "
-                            + MIN_FREQ + ".." + MAX_FREQ);
-                }
-                return intValue;
             }
+            final int intValue = super.getValueFromFreqString(freqString);
+            if (intValue < MIN_FREQ || intValue > MAX_FREQ) {
+                throw new RuntimeException("Shortcut freq out of range. Accepted range is "
+                        + MIN_FREQ + ".." + MAX_FREQ);
+            }
+            return intValue;
         }
 
         // As per getAssocMap(), this never returns null.
@@ -269,23 +264,12 @@
      * @return true if the file is in the unigram XML format, false otherwise
      */
     public static boolean isXmlUnigramDictionary(final String filename) {
-        BufferedReader reader = null;
-        try {
-            reader = new BufferedReader(new FileReader(new File(filename)));
+        try (final BufferedReader reader = new BufferedReader(
+                new InputStreamReader(new FileInputStream(filename), "UTF-8"))) {
             final String firstLine = reader.readLine();
             return firstLine.matches("^\\s*<wordlist .*>\\s*$");
-        } catch (FileNotFoundException e) {
+        } catch (final IOException e) {
             return false;
-        } catch (IOException e) {
-            return false;
-        } finally {
-            if (reader != null) {
-                try {
-                    reader.close();
-                } catch (IOException e) {
-                    // do nothing
-                }
-            }
         }
     }
 
@@ -300,8 +284,8 @@
      * @param bigrams the file to read the bigrams from, or null.
      * @return the in-memory representation of the dictionary.
      */
-    public static FusionDictionary readDictionaryXml(final InputStream unigrams,
-            final InputStream shortcuts, final InputStream bigrams)
+    public static FusionDictionary readDictionaryXml(final BufferedInputStream unigrams,
+            final BufferedInputStream shortcuts, final BufferedInputStream bigrams)
             throws SAXException, IOException, ParserConfigurationException {
         final SAXParserFactory factory = SAXParserFactory.newInstance();
         factory.setNamespaceAware(true);
@@ -350,8 +334,8 @@
      * @param destination a destination stream to write to.
      * @param dict the dictionary to write.
      */
-    public static void writeDictionaryXml(Writer destination, FusionDictionary dict)
-            throws IOException {
+    public static void writeDictionaryXml(final BufferedWriter destination,
+            final FusionDictionary dict) throws IOException {
         final TreeSet<WordProperty> wordPropertiesInDict = new TreeSet<>();
         for (WordProperty wordProperty : dict) {
             wordPropertiesInDict.add(wordProperty);
diff --git a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
index fccb654..0236a44 100644
--- a/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
+++ b/tools/dicttool/tests/com/android/inputmethod/latin/dicttool/BinaryDictOffdeviceUtilsTests.java
@@ -62,13 +62,13 @@
 
         final File dst = File.createTempFile("testGetRawDict", ".tmp");
         dst.deleteOnExit();
-
-        final OutputStream out = Compress.getCompressedStream(
+        try (final OutputStream out = Compress.getCompressedStream(
                 Compress.getCompressedStream(
                         Compress.getCompressedStream(
-                                new BufferedOutputStream(new FileOutputStream(dst)))));
-        final DictEncoder dictEncoder = new Ver2DictEncoder(out);
-        dictEncoder.writeDictionary(dict, new FormatOptions(2, false));
+                                new BufferedOutputStream(new FileOutputStream(dst)))))) {
+            final DictEncoder dictEncoder = new Ver2DictEncoder(out);
+            dictEncoder.writeDictionary(dict, new FormatOptions(2, false));
+        }
 
         // Test for an actually compressed dictionary and its contents
         final BinaryDictOffdeviceUtils.DecoderChainSpec decodeSpec =
@@ -96,11 +96,11 @@
         // Randomly create some 4k file containing garbage
         final File dst = File.createTempFile("testGetRawDict", ".tmp");
         dst.deleteOnExit();
-        final OutputStream out = new BufferedOutputStream(new FileOutputStream(dst));
-        for (int i = 0; i < 1024; ++i) {
-            out.write(0x12345678);
+        try (final OutputStream out = new BufferedOutputStream(new FileOutputStream(dst))) {
+            for (int i = 0; i < 1024; ++i) {
+                out.write(0x12345678);
+            }
         }
-        out.close();
 
         // Test that a random data file actually fails
         assertNull("Wrongly identified data file",
@@ -108,12 +108,12 @@
 
         final File gzDst = File.createTempFile("testGetRawDict", ".tmp");
         gzDst.deleteOnExit();
-        final OutputStream gzOut =
-                Compress.getCompressedStream(new BufferedOutputStream(new FileOutputStream(gzDst)));
-        for (int i = 0; i < 1024; ++i) {
-            gzOut.write(0x12345678);
+        try (final OutputStream gzOut = Compress.getCompressedStream(
+                new BufferedOutputStream(new FileOutputStream(gzDst)))) {
+            for (int i = 0; i < 1024; ++i) {
+                gzOut.write(0x12345678);
+            }
         }
-        gzOut.close();
 
         // Test that a compressed random data file actually fails
         assertNull("Wrongly identified data file",