Merge "Do not output string-encoded MotionEvent."
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index 50ae4dc..cd31740 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -75,7 +75,7 @@
     <string name="gesture_floating_preview_text_summary" msgid="4472696213996203533">"Onyesha neno lililopendekezwa unapoonyesha ishara"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Imehifadhiwa"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Nenda"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Ifuatayo"</string>
+    <string name="label_next_key" msgid="362972844525672568">"Inayofuata"</string>
     <string name="label_previous_key" msgid="1211868118071386787">"Iliyotangulia"</string>
     <string name="label_done_key" msgid="2441578748772529288">"Kwisha"</string>
     <string name="label_send_key" msgid="2815056534433717444">"Tuma"</string>
diff --git a/java/res/xml-sw600dp/keys_arabic3_left.xml b/java/res/xml-sw600dp/keys_arabic3_left.xml
new file mode 100644
index 0000000..0f2ccc0
--- /dev/null
+++ b/java/res/xml-sw600dp/keys_arabic3_left.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x0630;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0626;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-sw600dp/keys_farsi3_right.xml b/java/res/xml-sw600dp/keys_farsi3_right.xml
new file mode 100644
index 0000000..3c91ae9
--- /dev/null
+++ b/java/res/xml-sw600dp/keys_farsi3_right.xml
@@ -0,0 +1,32 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
+    <Key
+        latin:keyLabel="&#x0622;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x0686;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic1.xml b/java/res/xml-sw600dp/rowkeys_arabic1.xml
deleted file mode 100644
index 3c0acf1..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic1.xml
+++ /dev/null
@@ -1,112 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0636: "ض" ARABIC LETTER DAD
-         U+0661: "١" ARABIC-INDIC DIGIT ONE -->
-    <Key
-        latin:keyLabel="&#x0636;"
-        latin:keyHintLabel="1"
-        latin:additionalMoreKeys="1,&#x0661;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0635: "ص" ARABIC LETTER SAD
-         U+0662: "٢" ARABIC-INDIC DIGIT TWO -->
-    <Key
-        latin:keyLabel="&#x0635;"
-        latin:keyHintLabel="2"
-        latin:additionalMoreKeys="2,&#x0662;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062B: "ث" ARABIC LETTER THEH
-         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
-    <Key
-        latin:keyLabel="&#x062B;"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="3,&#x0663;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF
-         U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE
-         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
-    <Key
-        latin:keyLabel="&#x0642;"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4,&#x0664;"
-        latin:moreKeys="&#x06A8;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH
-         U+06A4: "ڤ" ARABIC LETTER VEH
-         U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
-         U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW
-         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
-    <Key
-        latin:keyLabel="&#x0641;"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="5,&#x0665;"
-        latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN
-         U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyHintLabel="6"
-        latin:additionalMoreKeys="6,&#x0666;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
-         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
-    <Key
-        latin:keyLabel="&#x0639;"
-        latin:keyHintLabel="7"
-        latin:additionalMoreKeys="7,&#x0667;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0647: "ه" ARABIC LETTER HEH
-         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
-         U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
-         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x0647;"
-        latin:keyHintLabel="8"
-        latin:additionalMoreKeys="8,&#x0668;"
-        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="9"
-        latin:additionalMoreKeys="9,&#x0669;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
-         U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
-    <Key
-        latin:keyLabel="&#x062D;"
-        latin:keyHintLabel="0"
-        latin:additionalMoreKeys="0,&#x0660;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
-         U+0686: "چ" ARABIC LETTER TCHEH -->
-    <Key
-        latin:keyLabel="&#x062C;"
-        latin:moreKeys="&#x0686;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic2.xml b/java/res/xml-sw600dp/rowkeys_arabic2.xml
deleted file mode 100644
index 00e69ac..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic2.xml
+++ /dev/null
@@ -1,94 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0634: "ش" ARABIC LETTER SHEEN
-         U+069C: "ڜ" ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-    <!-- TODO: DroidSansArabic lacks the glyph of U+069C ARABIC LETTER SEEN WITH THREE DOTS BELOW AND THREE DOTS ABOVE -->
-    <Key
-        latin:keyLabel="&#x0634;"
-        latin:moreKeys="&#x069C;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN -->
-    <Key
-        latin:keyLabel="&#x0633;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+064A: "ي" ARABIC LETTER YEH
-         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
-         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x064A;"
-        latin:moreKeys="&#x0626;,&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0628: "ب" ARABIC LETTER BEH
-         U+067E: "پ" ARABIC LETTER PEH -->
-    <Key
-        latin:keyLabel="&#x0628;"
-        latin:moreKeys="&#x067E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0644: "ل" ARABIC LETTER LAM
-         U+FEFB: "ﻻ" ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
-         U+0627: "ا" ARABIC LETTER ALEF
-         U+FEF7: "ﻷ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+FEF9: "ﻹ" ARABIC LIGATURE LAM WITH ALEF WITH HAMZA BELOW ISOLATED FORM
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
-         U+FEF5: "ﻵ" ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0644;"
-        latin:moreKeys="&#xFEFB;|&#x0644;&#x0627;,&#xFEF7;|&#x0644;&#x0623;,&#xFEF9;|&#x0644;&#x0625;,&#xFEF5;|&#x0644;&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0627: "ا" ARABIC LETTER ALEF
-         U+0621: "ء" ARABIC LETTER HAMZA
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH -->
-    <Key
-        latin:keyLabel="&#x062A;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0646: "ن" ARABIC LETTER NOON -->
-    <Key
-        latin:keyLabel="&#x0646;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0645: "م" ARABIC LETTER MEEM -->
-    <Key
-        latin:keyLabel="&#x0645;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0643: "ك" ARABIC LETTER KAF
-         U+06AF: "گ" ARABIC LETTER GAF
-         U+06A9: "ک" ARABIC LETTER KEHEH -->
-    <Key
-        latin:keyLabel="&#x0643;"
-        latin:moreKeys="&#x06AF;,&#x06A9;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
-    <Key
-        latin:keyLabel="&#x0637;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_arabic3.xml b/java/res/xml-sw600dp/rowkeys_arabic3.xml
deleted file mode 100644
index b0bcd78..0000000
--- a/java/res/xml-sw600dp/rowkeys_arabic3.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0626;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0621: "ء" ARABIC LETTER HAMZA -->
-    <Key
-        latin:keyLabel="&#x0621;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0624;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
-    <Key
-        latin:keyLabel="&#x0631;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
-    <Key
-        latin:keyLabel="&#x0630;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
-    <Key
-        latin:keyLabel="&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN
-         U+0698: "ژ" ARABIC LETTER JEH -->
-    <Key
-        latin:keyLabel="&#x0632;"
-        latin:moreKeys="&#x0698;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
-    <Key
-        latin:keyLabel="&#x0638;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
-    <Key
-        latin:keyLabel="&#x062F;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi1.xml b/java/res/xml-sw600dp/rowkeys_farsi1.xml
deleted file mode 100644
index 6dd9a65..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi1.xml
+++ /dev/null
@@ -1,107 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0636: "ض" ARABIC LETTER DAD
-         U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE -->
-    <Key
-        latin:keyLabel="&#x0636;"
-        latin:keyHintLabel="&#x06F1;"
-        latin:additionalMoreKeys="&#x06F1;,1"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0635: "ص" ARABIC LETTER SAD
-         U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
-    <Key
-        latin:keyLabel="&#x0635;"
-        latin:keyHintLabel="&#x06F2;"
-        latin:additionalMoreKeys="&#x06F2;,2"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062B: "ث" ARABIC LETTER THEH
-         U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
-    <Key
-        latin:keyLabel="&#x062B;"
-        latin:keyHintLabel="&#x06F3;"
-        latin:additionalMoreKeys="&#x06F3;,3"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF
-         U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
-    <Key
-        latin:keyLabel="&#x0642;"
-        latin:keyHintLabel="&#x06F4;"
-        latin:additionalMoreKeys="&#x06F4;,4"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH
-         U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x0641;"
-        latin:keyHintLabel="&#x06F5;"
-        latin:additionalMoreKeys="&#x06F5;,5"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN
-         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyHintLabel="&#x06F6;"
-        latin:additionalMoreKeys="&#x06F6;,6"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
-         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
-    <Key
-        latin:keyLabel="&#x0639;"
-        latin:keyHintLabel="&#x06F7;"
-        latin:additionalMoreKeys="&#x06F7;,7"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0647: "ه" ARABIC LETTER HEH
-         U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
-         U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
-         U+0647/U+0654: ARABIC LETTER HEH + ARABIC HAMZA ABOVE
-         U+0629: "ة" ARABIC LETTER TEH MARBUTA
-         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x0647;"
-        latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x0647;&#x0654;,&#x0629;,%"
-        latin:keyHintLabel="&#x06F8;"
-        latin:additionalMoreKeys="&#x06F8;,8"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="&#x06F9;"
-        latin:additionalMoreKeys="&#x06F9;,9"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
-         U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
-    <Key
-        latin:keyLabel="&#x062D;"
-        latin:keyHintLabel="&#x06F0;"
-        latin:additionalMoreKeys="&#x06F0;,0"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM -->
-    <Key
-        latin:keyLabel="&#x062C;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
-    <Key
-        latin:keyLabel="&#x0686;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi2.xml b/java/res/xml-sw600dp/rowkeys_farsi2.xml
deleted file mode 100644
index 3b759b6..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi2.xml
+++ /dev/null
@@ -1,84 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0634: "ش" ARABIC LETTER SHEEN -->
-    <Key
-        latin:keyLabel="&#x0634;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN -->
-    <Key
-        latin:keyLabel="&#x0633;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
-         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
-         U+064A: "ي" ARABIC LETTER YEH
-         U+FBE8: "ﯨ" ARABIC LETTER UIGHUR KAZAKH KIRGHIZ ALEF MAKSURA INITIAL FORM
-         U+0649: "ى" ARABIC LETTER ALEF MAKSURA -->
-    <Key
-        latin:keyLabel="&#x06CC;"
-        latin:moreKeys="&#x0626;,&#x064A;,&#xFBE8;|&#x0649;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0628: "ب" ARABIC LETTER BEH -->
-    <Key
-        latin:keyLabel="&#x0628;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0644: "ل" ARABIC LETTER LAM -->
-    <Key
-        latin:keyLabel="&#x0644;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0627: "ا" ARABIC LETTER ALEF
-         U+0621: "ء" ARABIC LETTER HAMZA
-         U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE
-         U+0623: "أ" ARABIC LETTER ALEF WITH HAMZA ABOVE
-         U+0671: "ٱ" ARABIC LETTER ALEF WASLA
-         U+0625: "إ" ARABIC LETTER ALEF WITH HAMZA BELOW -->
-    <Key
-        latin:keyLabel="&#x0627;"
-        latin:moreKeys="&#x0621;,&#x0622;,&#x0623;,&#x0671;,&#x0625;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH
-         U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
-    <Key
-        latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;,&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0646: "ن" ARABIC LETTER NOON -->
-    <Key
-        latin:keyLabel="&#x0646;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0645: "م" ARABIC LETTER MEEM -->
-    <Key
-        latin:keyLabel="&#x0645;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06A9: "ک" ARABIC LETTER KEHEH
-         U+0643: "ك" ARABIC LETTER KAF -->
-    <Key
-        latin:keyLabel="&#x06A9;"
-        latin:moreKeys="&#x0643;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
-    <Key
-        latin:keyLabel="&#x06AF;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rowkeys_farsi3.xml b/java/res/xml-sw600dp/rowkeys_farsi3.xml
deleted file mode 100644
index 3597618..0000000
--- a/java/res/xml-sw600dp/rowkeys_farsi3.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2012, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
--->
-
-<merge
-    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
->
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
-    <Key
-        latin:keyLabel="&#x0638;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
-    <Key
-        latin:keyLabel="&#x0637;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0698: "ژ" ARABIC LETTER JEH -->
-    <Key
-        latin:keyLabel="&#x0698;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN -->
-    <Key
-        latin:keyLabel="&#x0632;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
-    <Key
-        latin:keyLabel="&#x0631;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
-    <Key
-        latin:keyLabel="&#x0630;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
-    <Key
-        latin:keyLabel="&#x062F;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+067E: "پ" ARABIC LETTER PEH -->
-    <Key
-        latin:keyLabel="&#x067E;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW
-         U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:moreKeys="&#x0624;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0622: "آ" ARABIC LETTER ALEF WITH MADDA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0622;"
-        latin:keyLabelFlags="fontNormal" />
-</merge>
diff --git a/java/res/xml-sw600dp/rows_arabic.xml b/java/res/xml-sw600dp/rows_arabic.xml
index ec7c2ad..5a28d45 100644
--- a/java/res/xml-sw600dp/rows_arabic.xml
+++ b/java/res/xml-sw600dp/rows_arabic.xml
@@ -45,8 +45,7 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="4.091%p" />
+            latin:keyboardLayout="@xml/rowkeys_arabic3" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw600dp/rows_farsi.xml b/java/res/xml-sw600dp/rows_farsi.xml
index 52c2d93..a353b67 100644
--- a/java/res/xml-sw600dp/rows_farsi.xml
+++ b/java/res/xml-sw600dp/rows_farsi.xml
@@ -28,12 +28,6 @@
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
-    </Row>
-    <Row
-        latin:keyWidth="8.182%p"
-    >
-        <include
-            latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
             latin:keyStyle="deleteKeyStyle"
             latin:keyWidth="fillRight" />
@@ -42,12 +36,18 @@
         latin:keyWidth="8.182%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_farsi3"
-            latin:keyXPos="4.091%p" />
+            latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
             latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
+    <Row
+        latin:keyWidth="8.182%p"
+    >
+        <include
+            latin:keyboardLayout="@xml/rowkeys_farsi3"
+            latin:keyXPos="4.091%p" />
+    </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
 </merge>
diff --git a/java/res/xml-sw768dp/rows_arabic.xml b/java/res/xml-sw768dp/rows_arabic.xml
index 8b05d93..204f6d5 100644
--- a/java/res/xml-sw768dp/rows_arabic.xml
+++ b/java/res/xml-sw768dp/rows_arabic.xml
@@ -54,7 +54,7 @@
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="13.829%p" />
+            latin:keyXPos="6.602%p" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml-sw768dp/rows_farsi.xml b/java/res/xml-sw768dp/rows_farsi.xml
index 4b4c970..8d3fb05 100644
--- a/java/res/xml-sw768dp/rows_farsi.xml
+++ b/java/res/xml-sw768dp/rows_farsi.xml
@@ -32,6 +32,9 @@
             latin:keyWidth="7.969%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
+        <Key
+            latin:keyStyle="deleteKeyStyle"
+            latin:keyWidth="fillRight" />
     </Row>
     <Row
         latin:keyWidth="7.227%p"
@@ -39,22 +42,19 @@
         <Key
             latin:keyStyle="toSymbolKeyStyle"
             latin:keyLabelFlags="alignLeft"
-            latin:keyWidth="11.172%p"/>
+            latin:keyWidth="11.172%p" />
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi2" />
         <Key
-            latin:keyStyle="deleteKeyStyle"
+            latin:keyStyle="enterKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
     <Row
-        latin:keyWidth="7.186%p"
+        latin:keyWidth="7.227%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi3"
             latin:keyXPos="13.829%p" />
-        <Key
-            latin:keyStyle="enterKeyStyle"
-            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/keys_arabic3_left.xml b/java/res/xml/keys_arabic3_left.xml
new file mode 100644
index 0000000..157af4a
--- /dev/null
+++ b/java/res/xml/keys_arabic3_left.xml
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <Key
+        latin:keyLabel="&#x0630;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/keys_farsi3_right.xml b/java/res/xml/keys_farsi3_right.xml
new file mode 100644
index 0000000..77efb0a
--- /dev/null
+++ b/java/res/xml/keys_farsi3_right.xml
@@ -0,0 +1,28 @@
+<?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.
+*/
+-->
+
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x0686;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/method.xml b/java/res/xml/method.xml
index f30ef23..5e33601 100644
--- a/java/res/xml/method.xml
+++ b/java/res/xml/method.xml
@@ -21,9 +21,10 @@
 <!-- for the Input Method Manager. -->
 
 <!-- Supported subtypes
-    keyboard_locale: script_name/keyboard_layout_set[:keyboard_locale]
+    keyboard_locale: script_name/keyboard_layout_set
     af: Afrikaans/qwerty
     ar: Arabic/arabic
+    az: Azerbaijani/qwerty
     be: Belarusian/east_slavic
     bg: Bulgarian/bulgarian
     bg: Bulgarian/bulgarian_bds
@@ -51,6 +52,7 @@
     it: Italian/qwerty
     iw: Hebrew/hebrew        # "he" is official language code of Hebrew.
     ka: Georgian/georgian
+    kk: Kazakh/east_slavic
     ky: Kyrgyz/east_slavic
     lt: Lithuanian/qwerty
     lv: Latvian/qwerty
@@ -117,6 +119,13 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:subtypeId="0x2a362219"
+            android:imeSubtypeLocale="az"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="AsciiCapable,SupportTouchPositionCorrection"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x1dc3a859"
             android:imeSubtypeLocale="be"
             android:imeSubtypeMode="keyboard"
@@ -296,6 +305,13 @@
     />
     <subtype android:icon="@drawable/ic_subtype_keyboard"
             android:label="@string/subtype_generic"
+            android:subtypeId="0x2d73d2f6"
+            android:imeSubtypeLocale="kk"
+            android:imeSubtypeMode="keyboard"
+            android:imeSubtypeExtraValue="KeyboardLayoutSet=east_slavic"
+    />
+    <subtype android:icon="@drawable/ic_subtype_keyboard"
+            android:label="@string/subtype_generic"
             android:subtypeId="0x2e391c04"
             android:imeSubtypeLocale="ky"
             android:imeSubtypeMode="keyboard"
diff --git a/java/res/xml/rowkeys_arabic1.xml b/java/res/xml/rowkeys_arabic1.xml
index a4bef83..3c0acf1 100644
--- a/java/res/xml/rowkeys_arabic1.xml
+++ b/java/res/xml/rowkeys_arabic1.xml
@@ -35,74 +35,78 @@
         latin:keyHintLabel="2"
         latin:additionalMoreKeys="2,&#x0662;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+062B: "ث" ARABIC LETTER THEH
+         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x062B;"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="3,&#x0663;"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0642: "ق" ARABIC LETTER QAF
          U+06A8: "ڨ" ARABIC LETTER QAF WITH THREE DOTS ABOVE
-         U+0663: "٣" ARABIC-INDIC DIGIT THREE -->
+         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A8 ARABIC LETTER QAF WITH THREE DOTS ABOVE -->
     <Key
         latin:keyLabel="&#x0642;"
-        latin:keyHintLabel="3"
-        latin:additionalMoreKeys="3,&#x0663;"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="4,&#x0664;"
         latin:moreKeys="&#x06A8;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0641: "ف" ARABIC LETTER FEH
          U+06A4: "ڤ" ARABIC LETTER VEH
          U+06A2: "ڢ" ARABIC LETTER FEH WITH DOT MOVED BELOW
          U+06A5: "ڥ" ARABIC LETTER FEH WITH THREE DOTS BELOW
-         U+0664: "٤" ARABIC-INDIC DIGIT FOUR -->
+         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A2 ARABIC LETTER FEH WITH DOT MOVED BELOW -->
     <!-- TODO: DroidSansArabic lacks the glyph of U+06A5 ARABIC LETTER FEH WITH THREE DOTS BELOW -->
     <Key
         latin:keyLabel="&#x0641;"
-        latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4,&#x0664;"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="5,&#x0665;"
         latin:moreKeys="&#x06A4;,&#x06A2;,&#x06A5;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+063A: "غ" ARABIC LETTER GHAIN
-         U+0665: "٥" ARABIC-INDIC DIGIT FIVE -->
-    <Key
-        latin:keyLabel="&#x063A;"
-        latin:keyHintLabel="5"
-        latin:additionalMoreKeys="5,&#x0665;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
          U+0666: "٦" ARABIC-INDIC DIGIT SIX -->
     <Key
-        latin:keyLabel="&#x0639;"
+        latin:keyLabel="&#x063A;"
         latin:keyHintLabel="6"
         latin:additionalMoreKeys="6,&#x0666;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="7"
+        latin:additionalMoreKeys="7,&#x0667;"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0647: "ه" ARABIC LETTER HEH
          U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
          U+0647 U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
-         U+0667: "٧" ARABIC-INDIC DIGIT SEVEN -->
+         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
     <Key
         latin:keyLabel="&#x0647;"
-        latin:keyHintLabel="7"
-        latin:additionalMoreKeys="7,&#x0667;"
+        latin:keyHintLabel="8"
+        latin:additionalMoreKeys="8,&#x0668;"
         latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+0668: "٨" ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="8"
-        latin:additionalMoreKeys="8,&#x0668;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+0669: "٩" ARABIC-INDIC DIGIT NINE -->
     <Key
-        latin:keyLabel="&#x062D;"
+        latin:keyLabel="&#x062E;"
         latin:keyHintLabel="9"
         latin:additionalMoreKeys="9,&#x0669;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
-         U+0686: "چ" ARABIC LETTER TCHEH
+    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+0660: "٠" ARABIC-INDIC DIGIT ZERO -->
     <Key
-        latin:keyLabel="&#x062C;"
+        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="0"
         latin:additionalMoreKeys="0,&#x0660;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM
+         U+0686: "چ" ARABIC LETTER TCHEH -->
+    <Key
+        latin:keyLabel="&#x062C;"
         latin:moreKeys="&#x0686;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_arabic2.xml b/java/res/xml/rowkeys_arabic2.xml
index d733f64..00e69ac 100644
--- a/java/res/xml/rowkeys_arabic2.xml
+++ b/java/res/xml/rowkeys_arabic2.xml
@@ -68,11 +68,9 @@
         latin:keyLabel="&#x0627;"
         latin:moreKeys="&#x0621;,&#x0671;,&#x0623;,&#x0625;,&#x0622;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH -->
+    <!-- U+062A: "ت" ARABIC LETTER TEH -->
     <Key
         latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0646: "ن" ARABIC LETTER NOON -->
     <Key
@@ -89,4 +87,8 @@
         latin:keyLabel="&#x0643;"
         latin:moreKeys="&#x06AF;,&#x06A9;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <Key
+        latin:keyLabel="&#x0637;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_arabic3.xml b/java/res/xml/rowkeys_arabic3.xml
index e4e6948..8a17b4b 100644
--- a/java/res/xml/rowkeys_arabic3.xml
+++ b/java/res/xml/rowkeys_arabic3.xml
@@ -21,21 +21,33 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <include
+        latin:keyboardLayout="@xml/keys_arabic3_left" />
+    <!-- U+0621: "ء" ARABIC LETTER HAMZA -->
     <Key
-        latin:keyLabel="&#x0638;"
+        latin:keyLabel="&#x0621;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0637: "ط" ARABIC LETTER TAH -->
+    <!-- U+0624: "ؤ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
     <Key
-        latin:keyLabel="&#x0637;"
+        latin:keyLabel="&#x0624;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0630: "ذ" ARABIC LETTER THAL -->
+    <!-- U+0631: "ر" ARABIC LETTER REH -->
     <Key
-        latin:keyLabel="&#x0630;"
+        latin:keyLabel="&#x0631;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062F: "د" ARABIC LETTER DAL -->
+    <!-- U+0649: "ى" ARABIC LETTER ALEF MAKSURA
+         U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE -->
     <Key
-        latin:keyLabel="&#x062F;"
+        latin:keyLabel="&#x0649;"
+        latin:moreKeys="&#x0626;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <Key
+        latin:keyLabel="&#x0629;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0648: "و" ARABIC LETTER WAW -->
+    <Key
+        latin:keyLabel="&#x0648;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0632: "ز" ARABIC LETTER ZAIN
          U+0698: "ژ" ARABIC LETTER JEH -->
@@ -43,18 +55,12 @@
         latin:keyLabel="&#x0632;"
         latin:moreKeys="&#x0698;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0631: "ر" ARABIC LETTER REH -->
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
     <Key
-        latin:keyLabel="&#x0631;"
+        latin:keyLabel="&#x0638;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0629: "ة" ARABIC LETTER TEH MARBUTA -->
+    <!-- U+062F: "د" ARABIC LETTER DAL -->
     <Key
-        latin:keyLabel="&#x0629;"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+0648: "و" ARABIC LETTER WAW
-         U+0624: "ﺅ" ARABIC LETTER WAW WITH HAMZA ABOVE -->
-    <Key
-        latin:keyLabel="&#x0648;"
-        latin:moreKeys="&#x0624;"
+        latin:keyLabel="&#x062F;"
         latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_east_slavic1.xml b/java/res/xml/rowkeys_east_slavic1.xml
index c1b43bd..5b3b4b4 100644
--- a/java/res/xml/rowkeys_east_slavic1.xml
+++ b/java/res/xml/rowkeys_east_slavic1.xml
@@ -41,7 +41,8 @@
     <Key
         latin:keyLabel="&#x043A;"
         latin:keyHintLabel="4"
-        latin:additionalMoreKeys="4" />
+        latin:additionalMoreKeys="4"
+        latin:moreKeys="!text/more_keys_for_cyrillic_ka" />
     <!-- U+0435: "е" CYRILLIC SMALL LETTER IE -->
     <Key
         latin:keyLabel="&#x0435;"
diff --git a/java/res/xml/rowkeys_east_slavic2.xml b/java/res/xml/rowkeys_east_slavic2.xml
index 9743727..2e412f0 100644
--- a/java/res/xml/rowkeys_east_slavic2.xml
+++ b/java/res/xml/rowkeys_east_slavic2.xml
@@ -32,7 +32,8 @@
         latin:keyLabel="&#x0432;" />
     <!-- U+0430: "а" CYRILLIC SMALL LETTER A -->
     <Key
-        latin:keyLabel="&#x0430;" />
+        latin:keyLabel="&#x0430;"
+        latin:moreKeys="!text/more_keys_for_cyrillic_a" />
     <!-- U+043F: "п" CYRILLIC SMALL LETTER PE -->
     <Key
         latin:keyLabel="&#x043F;" />
@@ -53,5 +54,6 @@
     <Key
         latin:keyLabel="&#x0436;" />
     <Key
-        latin:keyLabel="!text/keylabel_for_east_slavic_row2_11" />
+        latin:keyLabel="!text/keylabel_for_east_slavic_row2_11"
+        latin:moreKeys="!text/more_keys_for_east_slavic_row2_11" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi1.xml b/java/res/xml/rowkeys_farsi1.xml
index 0ccf1ab..5a22a24 100644
--- a/java/res/xml/rowkeys_farsi1.xml
+++ b/java/res/xml/rowkeys_farsi1.xml
@@ -21,81 +21,83 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0635: "ص" ARABIC LETTER SAD
-         U+0636: "ض" ARABIC LETTER DAD
+    <!-- U+0636: "ض" ARABIC LETTER DAD
          U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE -->
     <Key
-        latin:keyLabel="&#x0635;"
-        latin:moreKeys="&#x0636;,%"
+        latin:keyLabel="&#x0636;"
         latin:keyHintLabel="&#x06F1;"
         latin:additionalMoreKeys="&#x06F1;,1"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0642: "ق" ARABIC LETTER QAF
+    <!-- U+0635: "ص" ARABIC LETTER SAD
          U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO -->
     <Key
-        latin:keyLabel="&#x0642;"
+        latin:keyLabel="&#x0635;"
         latin:keyHintLabel="&#x06F2;"
         latin:additionalMoreKeys="&#x06F2;,2"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0641: "ف" ARABIC LETTER FEH
+    <!-- U+062B: "ث" ARABIC LETTER THEH
          U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE -->
     <Key
-        latin:keyLabel="&#x0641;"
+        latin:keyLabel="&#x062B;"
         latin:keyHintLabel="&#x06F3;"
         latin:additionalMoreKeys="&#x06F3;,3"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+    <!-- U+0642: "ق" ARABIC LETTER QAF
          U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR -->
     <Key
-        latin:keyLabel="&#x063A;"
+        latin:keyLabel="&#x0642;"
         latin:keyHintLabel="&#x06F4;"
         latin:additionalMoreKeys="&#x06F4;,4"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0639: "ع" ARABIC LETTER AIN
+    <!-- U+0641: "ف" ARABIC LETTER FEH
          U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE -->
     <Key
-        latin:keyLabel="&#x0639;"
+        latin:keyLabel="&#x0641;"
         latin:keyHintLabel="&#x06F5;"
         latin:additionalMoreKeys="&#x06F5;,5"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+063A: "غ" ARABIC LETTER GHAIN
+         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+    <Key
+        latin:keyLabel="&#x063A;"
+        latin:keyHintLabel="&#x06F6;"
+        latin:additionalMoreKeys="&#x06F6;,6"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0639: "ع" ARABIC LETTER AIN
+         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
+    <Key
+        latin:keyLabel="&#x0639;"
+        latin:keyHintLabel="&#x06F7;"
+        latin:additionalMoreKeys="&#x06F7;,7"
+        latin:keyLabelFlags="fontNormal" />
     <!-- U+0647: "ه" ARABIC LETTER HEH
          U+FEEB: "ﻫ" ARABIC LETTER HEH INITIAL FORM
          U+0647/U+200D: ARABIC LETTER HEH + ZERO WIDTH JOINER
          U+0647/U+0654: ARABIC LETTER HEH + ARABIC HAMZA ABOVE
          U+0629: "ة" ARABIC LETTER TEH MARBUTA
-         U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX -->
+         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
     <Key
         latin:keyLabel="&#x0647;"
         latin:moreKeys="&#xFEEB;|&#x0647;&#x200D;,&#x0647;&#x0654;,&#x0629;,%"
-        latin:keyHintLabel="&#x06F6;"
-        latin:additionalMoreKeys="&#x06F6;,6"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062E: "خ" ARABIC LETTER KHAH
-         U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN -->
-    <Key
-        latin:keyLabel="&#x062E;"
-        latin:keyHintLabel="&#x06F7;"
-        latin:additionalMoreKeys="&#x06F7;,7"
-        latin:keyLabelFlags="fontNormal" />
-    <!-- U+062D: "ح" ARABIC LETTER HAH
-         U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT -->
-    <Key
-        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="&#x06F8;"
         latin:additionalMoreKeys="&#x06F8;,8"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+062C: "ج" ARABIC LETTER JEEM
+    <!-- U+062E: "خ" ARABIC LETTER KHAH
          U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE -->
     <Key
-        latin:keyLabel="&#x062C;"
+        latin:keyLabel="&#x062E;"
         latin:keyHintLabel="&#x06F9;"
         latin:additionalMoreKeys="&#x06F9;,9"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0686: "چ" ARABIC LETTER TCHEH
+    <!-- U+062D: "ح" ARABIC LETTER HAH
          U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO -->
     <Key
-        latin:keyLabel="&#x0686;"
+        latin:keyLabel="&#x062D;"
         latin:keyHintLabel="&#x06F0;"
         latin:additionalMoreKeys="&#x06F0;,0"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+062C: "ج" ARABIC LETTER JEEM -->
+    <Key
+        latin:keyLabel="&#x062C;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi2.xml b/java/res/xml/rowkeys_farsi2.xml
index 4b6abe2..fc6789e 100644
--- a/java/res/xml/rowkeys_farsi2.xml
+++ b/java/res/xml/rowkeys_farsi2.xml
@@ -25,11 +25,9 @@
     <Key
         latin:keyLabel="&#x0634;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0633: "س" ARABIC LETTER SEEN
-         U+0636: "ض" ARABIC LETTER DAD -->
+    <!-- U+0633: "س" ARABIC LETTER SEEN -->
     <Key
         latin:keyLabel="&#x0633;"
-        latin:moreKeys="&#x0636;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+06CC: "ی" ARABIC LETTER FARSI YEH
          U+0626: "ئ" ARABIC LETTER YEH WITH HAMZA ABOVE
@@ -59,11 +57,10 @@
         latin:moreKeys="&#x0621;,&#x0622;,&#x0623;,&#x0671;,&#x0625;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+062A: "ت" ARABIC LETTER TEH
-         U+062B: "ﺙ" ARABIC LETTER THEH
          U+0629: "ة": ARABIC LETTER TEH MARBUTA -->
     <Key
         latin:keyLabel="&#x062A;"
-        latin:moreKeys="&#x062B;,&#x0629;"
+        latin:moreKeys="&#x0629;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0646: "ن" ARABIC LETTER NOON -->
     <Key
@@ -79,4 +76,8 @@
         latin:keyLabel="&#x06A9;"
         latin:moreKeys="&#x0643;"
         latin:keyLabelFlags="fontNormal" />
+    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
+    <Key
+        latin:keyLabel="&#x06AF;"
+        latin:keyLabelFlags="fontNormal" />
 </merge>
diff --git a/java/res/xml/rowkeys_farsi3.xml b/java/res/xml/rowkeys_farsi3.xml
index 7d2e81f..98949f4 100644
--- a/java/res/xml/rowkeys_farsi3.xml
+++ b/java/res/xml/rowkeys_farsi3.xml
@@ -21,17 +21,21 @@
 <merge
     xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
 >
-    <!-- U+0637: "ط" ARABIC LETTER TAH
-         U+0638: "ظ" ARABIC LETTER ZAH -->
+    <!-- U+0638: "ظ" ARABIC LETTER ZAH -->
+    <Key
+        latin:keyLabel="&#x0638;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0637: "ط" ARABIC LETTER TAH -->
     <Key
         latin:keyLabel="&#x0637;"
-        latin:moreKeys="&#x0638;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+0632: "ز" ARABIC LETTER ZAIN
-         U+0698: "ژ" ARABIC LETTER JEH -->
+    <!-- U+0698: "ژ" ARABIC LETTER JEH -->
+    <Key
+        latin:keyLabel="&#x0698;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0632: "ز" ARABIC LETTER ZAIN -->
     <Key
         latin:keyLabel="&#x0632;"
-        latin:moreKeys="&#x0698;"
         latin:keyLabelFlags="fontNormal" />
     <!-- U+0631: "ر" ARABIC LETTER REH -->
     <Key
@@ -55,8 +59,6 @@
         latin:keyLabel="&#x0648;"
         latin:moreKeys="&#x0624;"
         latin:keyLabelFlags="fontNormal" />
-    <!-- U+06AF: "گ" ARABIC LETTER GAF -->
-    <Key
-        latin:keyLabel="&#x06AF;"
-        latin:keyLabelFlags="fontNormal" />
+    <include
+        latin:keyboardLayout="@xml/keys_farsi3_right" />
 </merge>
diff --git a/java/res/xml/rows_arabic.xml b/java/res/xml/rows_arabic.xml
index 6449af2..798c23e 100644
--- a/java/res/xml/rows_arabic.xml
+++ b/java/res/xml/rows_arabic.xml
@@ -24,27 +24,25 @@
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic1" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_arabic2" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_arabic3"
-            latin:keyXPos="5.0%p" />
+            latin:keyboardLayout="@xml/rowkeys_arabic3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/res/xml/rows_farsi.xml b/java/res/xml/rows_farsi.xml
index cc0c526..c74614f 100644
--- a/java/res/xml/rows_farsi.xml
+++ b/java/res/xml/rows_farsi.xml
@@ -24,27 +24,25 @@
     <include
         latin:keyboardLayout="@xml/key_styles_common" />
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi1" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
             latin:keyboardLayout="@xml/rowkeys_farsi2" />
     </Row>
     <Row
-        latin:keyWidth="10%p"
+        latin:keyWidth="9.091%p"
     >
         <include
-            latin:keyboardLayout="@xml/rowkeys_farsi3"
-            latin:keyXPos="5.0%p" />
+            latin:keyboardLayout="@xml/rowkeys_farsi3" />
         <Key
             latin:keyStyle="deleteKeyStyle"
-            latin:keyWidth="fillRight"
-            latin:visualInsetsLeft="1%p" />
+            latin:keyWidth="fillRight" />
     </Row>
     <include
         latin:keyboardLayout="@xml/row_qwerty4" />
diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
index 3bed2c7..d5e638e 100644
--- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
+++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java
@@ -28,8 +28,8 @@
 
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.Utils;
 
 import java.util.LinkedList;
 import java.util.Queue;
@@ -144,7 +144,7 @@
             // DownloadManager also stupidly cuts the extension to replace with its own that it
             // gets from the content-type. We need to circumvent this.
             final String disambiguator = "#" + System.currentTimeMillis()
-                    + Utils.getVersionName(context) + ".dict";
+                    + ApplicationUtils.getVersionName(context) + ".dict";
             final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator);
             final Request request = new Request(uri);
 
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
index 6e3dd71..767f895 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryService.java
@@ -22,7 +22,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -30,6 +29,7 @@
 
 import java.util.Locale;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Service that handles background tasks for the dictionary provider.
@@ -77,19 +77,19 @@
      * How often, in milliseconds, we want to update the metadata. This is a
      * floor value; actually, it may happen several hours later, or even more.
      */
-    private static final long UPDATE_FREQUENCY = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long UPDATE_FREQUENCY = TimeUnit.DAYS.toMillis(4);
 
     /**
      * We are waked around midnight, local time. We want to wake between midnight and 6 am,
      * roughly. So use a random time between 0 and this delay.
      */
-    private static final int MAX_ALARM_DELAY = 6 * ((int)AlarmManager.INTERVAL_HOUR);
+    private static final int MAX_ALARM_DELAY = (int)TimeUnit.HOURS.toMillis(6);
 
     /**
      * How long we consider a "very long time". If no update took place in this time,
      * the content provider will trigger an update in the background.
      */
-    private static final long VERY_LONG_TIME = 14 * DateUtils.DAY_IN_MILLIS;
+    private static final long VERY_LONG_TIME = TimeUnit.DAYS.toMillis(14);
 
     /**
      * The last seen start Id. This must be stored because we must only call stopSelfResult() with
@@ -170,7 +170,7 @@
             checkTimeAndMaybeSetupUpdateAlarm(context);
         } else if (DictionaryPackConstants.UPDATE_NOW_INTENT_ACTION.equals(intent.getAction())) {
             // Intent to trigger an update now.
-            UpdateHandler.update(context, false);
+            UpdateHandler.tryUpdate(context, false);
         } else {
             UpdateHandler.downloadFinished(context, intent);
         }
@@ -221,7 +221,7 @@
      */
     public static void updateNowIfNotUpdatedInAVeryLongTime(final Context context) {
         if (!isLastUpdateAtLeastThisOld(context, VERY_LONG_TIME)) return;
-        UpdateHandler.update(context, false);
+        UpdateHandler.tryUpdate(context, false);
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 4b89d20..7bbd041 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -30,6 +30,7 @@
 import android.preference.Preference;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.animation.AnimationUtils;
@@ -104,9 +105,16 @@
 
     @Override
     public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
-        mUpdateNowMenu = menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
-        mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-        refreshNetworkState();
+        final String metadataUri =
+                MetadataDbHelper.getMetadataUriAsString(getActivity(), mClientId);
+        // We only add the "Refresh" button if we have a non-empty URL to refresh from. If the
+        // URL is empty, of course we can't refresh so it makes no sense to display this.
+        if (!TextUtils.isEmpty(metadataUri)) {
+            mUpdateNowMenu =
+                    menu.add(Menu.NONE, MENU_UPDATE_NOW, 0, R.string.check_for_updates_now);
+            mUpdateNowMenu.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            refreshNetworkState();
+        }
     }
 
     @Override
@@ -353,7 +361,12 @@
         new Thread("updateByHand") {
             @Override
             public void run() {
-                UpdateHandler.update(activity, true);
+                // We call tryUpdate(), which returns whether we could successfully start an update.
+                // If we couldn't, we'll never receive the end callback, so we stop the loading
+                // animation and return to the previous screen.
+                if (!UpdateHandler.tryUpdate(activity, true)) {
+                    stopLoadingAnimation();
+                }
             }
         }.start();
     }
@@ -368,7 +381,9 @@
     private void startLoadingAnimation() {
         mLoadingView.setVisibility(View.VISIBLE);
         getView().setVisibility(View.GONE);
-        mUpdateNowMenu.setTitle(R.string.cancel);
+        // We come here when the menu element is pressed so presumably it can't be null. But
+        // better safe than sorry.
+        if (null != mUpdateNowMenu) mUpdateNowMenu.setTitle(R.string.cancel);
     }
 
     private void stopLoadingAnimation() {
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index 719f24e..f66ef87 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -38,8 +38,8 @@
 import com.android.inputmethod.compat.ConnectivityManagerCompatUtils;
 import com.android.inputmethod.compat.DownloadManagerCompatUtils;
 import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
-import com.android.inputmethod.latin.utils.Utils;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -173,14 +173,15 @@
      * Download latest metadata from the server through DownloadManager for all known clients
      * @param context The context for retrieving resources
      * @param updateNow Whether we should update NOW, or respect bandwidth policies
+     * @return true if an update successfully started, false otherwise.
      */
-    public static void update(final Context context, final boolean updateNow) {
+    public static boolean tryUpdate(final Context context, final boolean updateNow) {
         // TODO: loop through all clients instead of only doing the default one.
         final TreeSet<String> uris = new TreeSet<String>();
         final Cursor cursor = MetadataDbHelper.queryClientIds(context);
-        if (null == cursor) return;
+        if (null == cursor) return false;
         try {
-            if (!cursor.moveToFirst()) return;
+            if (!cursor.moveToFirst()) return false;
             do {
                 final String clientId = cursor.getString(0);
                 final String metadataUri =
@@ -192,6 +193,7 @@
         } finally {
             cursor.close();
         }
+        boolean started = false;
         for (final String metadataUri : uris) {
             if (!TextUtils.isEmpty(metadataUri)) {
                 // If the metadata URI is empty, that means we should never update it at all.
@@ -200,8 +202,10 @@
                 // is a bug and it happens anyway, doing nothing is the right thing to do.
                 // For more information, {@see DictionaryProvider#insert(Uri, ContentValues)}.
                 updateClientsWithMetadataUri(context, updateNow, metadataUri);
+                started = true;
             }
         }
+        return started;
     }
 
     /**
@@ -218,7 +222,7 @@
         // DownloadManager also stupidly cuts the extension to replace with its own that it
         // gets from the content-type. We need to circumvent this.
         final String disambiguator = "#" + System.currentTimeMillis()
-                + Utils.getVersionName(context) + ".json";
+                + ApplicationUtils.getVersionName(context) + ".json";
         final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator));
         DebugLogUtils.l("Request =", metadataRequest);
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
index 9eeee5b..00ea20d 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardActionListener.java
@@ -20,7 +20,6 @@
 import com.android.inputmethod.latin.InputPointers;
 
 public interface KeyboardActionListener {
-
     /**
      * Called when the user presses a key. This is sent before the {@link #onCodeInput} is called.
      * For keys that repeat, this is only called once.
@@ -99,9 +98,9 @@
      */
     public boolean onCustomRequest(int requestCode);
 
-    public static class Adapter implements KeyboardActionListener {
-        public static final Adapter EMPTY_LISTENER = new Adapter();
+    public static final KeyboardActionListener EMPTY_LISTENER = new Adapter();
 
+    public static class Adapter implements KeyboardActionListener {
         @Override
         public void onPressKey(int primaryCode, boolean isSinglePointer) {}
         @Override
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 4323f71..98e2bae 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -31,7 +31,6 @@
 import com.android.inputmethod.keyboard.KeyboardLayoutSet.KeyboardLayoutSetException;
 import com.android.inputmethod.keyboard.PointerTracker.TimerProxy;
 import com.android.inputmethod.keyboard.internal.KeyboardState;
-import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.InputView;
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
@@ -210,7 +209,6 @@
     }
 
     public void onPressKey(final int code, final boolean isSinglePointer) {
-        hapticAndAudioFeedback(code);
         mState.onPressKey(code, isSinglePointer, mLatinIME.getCurrentAutoCapsState());
     }
 
@@ -299,13 +297,6 @@
                 ? keyboardView.getTimerProxy().isInDoubleTapShiftKeyTimeout() : false;
     }
 
-    private void hapticAndAudioFeedback(final int code) {
-        if (mKeyboardView == null || mKeyboardView.isInSlidingKeyInput()) {
-            return;
-        }
-        AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, mKeyboardView);
-    }
-
     /**
      * Updates state machine to figure out when to automatically switch back to the previous mode.
      */
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4cee4cf..254b20b 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -37,6 +37,7 @@
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.HashSet;
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 8a926d6..f85e604 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -57,10 +57,8 @@
 import com.android.inputmethod.keyboard.internal.PreviewPlacerView;
 import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview;
 import com.android.inputmethod.keyboard.internal.TouchScreenRegulator;
-import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.DebugSettings;
-import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.Settings;
@@ -72,7 +70,9 @@
 import com.android.inputmethod.latin.utils.ResourceUtils;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.StringUtils;
-import com.android.inputmethod.latin.utils.Utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
+import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.util.Locale;
@@ -240,11 +240,15 @@
                 break;
             case MSG_REPEAT_KEY:
                 final Key currentKey = tracker.getKey();
-                if (currentKey != null && currentKey.mCode == msg.arg1) {
-                    tracker.onRepeatKey(currentKey);
-                    AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
-                            currentKey.mCode, keyboardView);
+                final int code = msg.arg1;
+                if (currentKey != null && currentKey.mCode == code) {
                     startKeyRepeatTimer(tracker, mKeyRepeatInterval);
+                    startTypingStateTimer(currentKey);
+                    final KeyboardActionListener listener =
+                            keyboardView.getKeyboardActionListener();
+                    listener.onPressKey(code, true /* isSinglePointer */);
+                    listener.onCodeInput(code,
+                            Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
                 }
                 break;
             case MSG_LONGPRESS_KEY:
@@ -564,6 +568,8 @@
                 altCodeKeyWhileTypingFadeoutAnimatorResId, this);
         mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
                 altCodeKeyWhileTypingFadeinAnimatorResId, this);
+
+        mKeyboardActionListener = KeyboardActionListener.EMPTY_LISTENER;
     }
 
     private ObjectAnimator loadObjectAnimator(final int resId, final Object target) {
@@ -977,39 +983,28 @@
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
             ResearchLogger.mainKeyboardView_onLongPress();
         }
-        final int code = key.mCode;
+        final KeyboardActionListener listener = mKeyboardActionListener;
         if (key.hasNoPanelAutoMoreKey()) {
-            final int embeddedCode = key.mMoreKeys[0].mCode;
+            final int moreKeyCode = key.mMoreKeys[0].mCode;
             tracker.onLongPressed();
-            invokeCodeInput(embeddedCode);
-            invokeReleaseKey(code);
-            AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(code, this);
+            listener.onPressKey(moreKeyCode, true /* isSinglePointer */);
+            listener.onCodeInput(moreKeyCode,
+                    Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
+            listener.onReleaseKey(moreKeyCode, false /* withSliding */);
             return;
         }
+        final int code = key.mCode;
         if (code == Constants.CODE_SPACE || code == Constants.CODE_LANGUAGE_SWITCH) {
             // Long pressing the space key invokes IME switcher dialog.
-            if (invokeCustomRequest(LatinIME.CODE_SHOW_INPUT_METHOD_PICKER)) {
+            if (listener.onCustomRequest(Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER)) {
                 tracker.onLongPressed();
-                invokeReleaseKey(code);
+                listener.onReleaseKey(code, false /* withSliding */);
                 return;
             }
         }
         openMoreKeysPanel(key, tracker);
     }
 
-    private boolean invokeCustomRequest(final int requestCode) {
-        return mKeyboardActionListener.onCustomRequest(requestCode);
-    }
-
-    private void invokeCodeInput(final int code) {
-        mKeyboardActionListener.onCodeInput(
-                code, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
-    }
-
-    private void invokeReleaseKey(final int code) {
-        mKeyboardActionListener.onReleaseKey(code, false);
-    }
-
     private void openMoreKeysPanel(final Key key, final PointerTracker tracker) {
         final MoreKeysPanel moreKeysPanel = onCreateMoreKeysPanel(key, getContext());
         if (moreKeysPanel == null) {
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
index 51f5446..d29e638 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java
@@ -29,6 +29,7 @@
 import com.android.inputmethod.keyboard.internal.MoreKeySpec;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreKeysKeyboard extends Keyboard {
     private final int mDefaultKeyCoordX;
diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
index 7b14259..5320759 100644
--- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java
+++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java
@@ -175,7 +175,7 @@
     private DrawingProxy mDrawingProxy;
     private TimerProxy mTimerProxy;
     private KeyDetector mKeyDetector;
-    private KeyboardActionListener mListener = KeyboardActionListener.Adapter.EMPTY_LISTENER;
+    private KeyboardActionListener mListener = KeyboardActionListener.EMPTY_LISTENER;
 
     private Keyboard mKeyboard;
     private int mPhantonSuddenMoveThreshold;
@@ -1263,13 +1263,8 @@
         if (!key.isRepeatable()) return;
         // Don't start key repeat when we are in sliding input mode.
         if (mIsInSlidingKeyInput) return;
-        onRepeatKey(key);
-        mTimerProxy.startKeyRepeatTimer(this);
-    }
-
-    public void onRepeatKey(final Key key) {
         detectAndSendKey(key, key.mX, key.mY, SystemClock.uptimeMillis());
-        mTimerProxy.startTypingStateTimer(key);
+        mTimerProxy.startKeyRepeatTimer(this);
     }
 
     private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime,
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
index 370d0cb..8ead44c 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java
@@ -81,9 +81,6 @@
     private boolean mPrevSymbolsKeyboardWasShifted;
     private int mRecapitalizeMode;
 
-    // For handling long press.
-    private boolean mLongPressShiftLockFired;
-
     // For handling double tap.
     private boolean mIsInAlphabetUnshiftedFromShifted;
     private boolean mIsInDoubleTapShiftKey;
@@ -325,10 +322,11 @@
         }
         if (code == Constants.CODE_SHIFT) {
             onPressShift();
+        } else if (code == Constants.CODE_CAPSLOCK) {
+            // Nothing to do here. See {@link #onReleaseKey(int,boolean)}.
         } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
             onPressSymbol();
         } else {
-            mLongPressShiftLockFired = false;
             mShiftKeyState.onOtherKeyPressed();
             mSymbolKeyState.onOtherKeyPressed();
             // It is required to reset the auto caps state when all of the following conditions
@@ -356,6 +354,8 @@
         }
         if (code == Constants.CODE_SHIFT) {
             onReleaseShift(withSliding);
+        } else if (code == Constants.CODE_CAPSLOCK) {
+            setShiftLocked(!mAlphabetShiftState.isShiftLocked());
         } else if (code == Constants.CODE_SWITCH_ALPHA_SYMBOL) {
             onReleaseSymbol(withSliding);
         }
@@ -437,7 +437,6 @@
     }
 
     private void onPressShift() {
-        mLongPressShiftLockFired = false;
         // If we are recapitalizing, we don't do any of the normal processing, including
         // importantly the double tap timer.
         if (RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE != mRecapitalizeMode) {
@@ -499,8 +498,6 @@
                 // Double tap shift key has been handled in {@link #onPressShift}, so that just
                 // ignore this release shift key here.
                 mIsInDoubleTapShiftKey = false;
-            } else if (mLongPressShiftLockFired) {
-                setShiftLocked(!mAlphabetShiftState.isShiftLocked());
             } else if (mShiftKeyState.isChording()) {
                 if (mAlphabetShiftState.isShiftLockShifted()) {
                     // After chording input while shift locked state.
@@ -610,12 +607,6 @@
             break;
         }
 
-        if (code == Constants.CODE_CAPSLOCK) {
-            // Changing shift lock state will be handled at {@link #onPressShift()} when the shift
-            // key is released.
-            mLongPressShiftLockFired = true;
-        }
-
         // If the code is a letter, update keyboard shift state.
         if (Constants.isLetterCode(code)) {
             updateAlphabetShiftState(autoCaps, RecapitalizeStatus.NOT_A_RECAPITALIZE_MODE);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 467c15f..1594df7 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -133,122 +133,125 @@
         /* 28 */ "keylabel_for_east_slavic_row2_11",
         /* 29 */ "keylabel_for_east_slavic_row3_5",
         /* 30 */ "more_keys_for_cyrillic_u",
-        /* 31 */ "more_keys_for_cyrillic_en",
-        /* 32 */ "more_keys_for_cyrillic_ghe",
-        /* 33 */ "more_keys_for_east_slavic_row2_1",
-        /* 34 */ "more_keys_for_cyrillic_o",
-        /* 35 */ "more_keys_for_cyrillic_soft_sign",
-        /* 36 */ "keylabel_for_south_slavic_row1_6",
-        /* 37 */ "keylabel_for_south_slavic_row2_11",
-        /* 38 */ "keylabel_for_south_slavic_row3_1",
-        /* 39 */ "keylabel_for_south_slavic_row3_8",
-        /* 40 */ "more_keys_for_cyrillic_ie",
-        /* 41 */ "more_keys_for_cyrillic_i",
-        /* 42 */ "label_to_alpha_key",
-        /* 43 */ "single_quotes",
-        /* 44 */ "double_quotes",
-        /* 45 */ "single_angle_quotes",
-        /* 46 */ "double_angle_quotes",
-        /* 47 */ "more_keys_for_currency_dollar",
-        /* 48 */ "keylabel_for_currency_generic",
-        /* 49 */ "more_keys_for_currency_generic",
-        /* 50 */ "more_keys_for_punctuation",
-        /* 51 */ "more_keys_for_star",
-        /* 52 */ "more_keys_for_bullet",
-        /* 53 */ "more_keys_for_plus",
-        /* 54 */ "more_keys_for_left_parenthesis",
-        /* 55 */ "more_keys_for_right_parenthesis",
-        /* 56 */ "more_keys_for_less_than",
-        /* 57 */ "more_keys_for_greater_than",
-        /* 58 */ "more_keys_for_arabic_diacritics",
-        /* 59 */ "keyhintlabel_for_arabic_diacritics",
-        /* 60 */ "keylabel_for_symbols_1",
-        /* 61 */ "keylabel_for_symbols_2",
-        /* 62 */ "keylabel_for_symbols_3",
-        /* 63 */ "keylabel_for_symbols_4",
-        /* 64 */ "keylabel_for_symbols_5",
-        /* 65 */ "keylabel_for_symbols_6",
-        /* 66 */ "keylabel_for_symbols_7",
-        /* 67 */ "keylabel_for_symbols_8",
-        /* 68 */ "keylabel_for_symbols_9",
-        /* 69 */ "keylabel_for_symbols_0",
-        /* 70 */ "label_to_symbol_key",
-        /* 71 */ "label_to_symbol_with_microphone_key",
-        /* 72 */ "additional_more_keys_for_symbols_1",
-        /* 73 */ "additional_more_keys_for_symbols_2",
-        /* 74 */ "additional_more_keys_for_symbols_3",
-        /* 75 */ "additional_more_keys_for_symbols_4",
-        /* 76 */ "additional_more_keys_for_symbols_5",
-        /* 77 */ "additional_more_keys_for_symbols_6",
-        /* 78 */ "additional_more_keys_for_symbols_7",
-        /* 79 */ "additional_more_keys_for_symbols_8",
-        /* 80 */ "additional_more_keys_for_symbols_9",
-        /* 81 */ "additional_more_keys_for_symbols_0",
-        /* 82 */ "more_keys_for_symbols_1",
-        /* 83 */ "more_keys_for_symbols_2",
-        /* 84 */ "more_keys_for_symbols_3",
-        /* 85 */ "more_keys_for_symbols_4",
-        /* 86 */ "more_keys_for_symbols_5",
-        /* 87 */ "more_keys_for_symbols_6",
-        /* 88 */ "more_keys_for_symbols_7",
-        /* 89 */ "more_keys_for_symbols_8",
-        /* 90 */ "more_keys_for_symbols_9",
-        /* 91 */ "more_keys_for_symbols_0",
-        /* 92 */ "keylabel_for_comma",
-        /* 93 */ "more_keys_for_comma",
-        /* 94 */ "keylabel_for_symbols_question",
-        /* 95 */ "keylabel_for_symbols_semicolon",
-        /* 96 */ "keylabel_for_symbols_percent",
-        /* 97 */ "more_keys_for_symbols_exclamation",
-        /* 98 */ "more_keys_for_symbols_question",
-        /* 99 */ "more_keys_for_symbols_semicolon",
-        /* 100 */ "more_keys_for_symbols_percent",
-        /* 101 */ "keylabel_for_tablet_comma",
-        /* 102 */ "keyhintlabel_for_tablet_comma",
-        /* 103 */ "more_keys_for_tablet_comma",
-        /* 104 */ "keyhintlabel_for_tablet_period",
-        /* 105 */ "more_keys_for_tablet_period",
-        /* 106 */ "keylabel_for_apostrophe",
-        /* 107 */ "keyhintlabel_for_apostrophe",
-        /* 108 */ "more_keys_for_apostrophe",
-        /* 109 */ "more_keys_for_q",
-        /* 110 */ "more_keys_for_x",
-        /* 111 */ "keylabel_for_q",
-        /* 112 */ "keylabel_for_w",
-        /* 113 */ "keylabel_for_y",
-        /* 114 */ "keylabel_for_x",
-        /* 115 */ "keylabel_for_spanish_row2_10",
-        /* 116 */ "more_keys_for_am_pm",
-        /* 117 */ "settings_as_more_key",
-        /* 118 */ "shortcut_as_more_key",
-        /* 119 */ "action_next_as_more_key",
-        /* 120 */ "action_previous_as_more_key",
-        /* 121 */ "label_to_more_symbol_key",
-        /* 122 */ "label_to_more_symbol_for_tablet_key",
-        /* 123 */ "label_tab_key",
-        /* 124 */ "label_to_phone_numeric_key",
-        /* 125 */ "label_to_phone_symbols_key",
-        /* 126 */ "label_time_am",
-        /* 127 */ "label_time_pm",
-        /* 128 */ "label_to_symbol_key_pcqwerty",
-        /* 129 */ "keylabel_for_popular_domain",
-        /* 130 */ "more_keys_for_popular_domain",
-        /* 131 */ "more_keys_for_smiley",
-        /* 132 */ "single_laqm_raqm",
-        /* 133 */ "single_laqm_raqm_rtl",
-        /* 134 */ "single_raqm_laqm",
-        /* 135 */ "double_laqm_raqm",
-        /* 136 */ "double_laqm_raqm_rtl",
-        /* 137 */ "double_raqm_laqm",
-        /* 138 */ "single_lqm_rqm",
-        /* 139 */ "single_9qm_lqm",
-        /* 140 */ "single_9qm_rqm",
-        /* 141 */ "double_lqm_rqm",
-        /* 142 */ "double_9qm_lqm",
-        /* 143 */ "double_9qm_rqm",
-        /* 144 */ "more_keys_for_single_quote",
-        /* 145 */ "more_keys_for_double_quote",
-        /* 146 */ "more_keys_for_tablet_double_quote",
+        /* 31 */ "more_keys_for_cyrillic_ka",
+        /* 32 */ "more_keys_for_cyrillic_en",
+        /* 33 */ "more_keys_for_cyrillic_ghe",
+        /* 34 */ "more_keys_for_east_slavic_row2_1",
+        /* 35 */ "more_keys_for_cyrillic_a",
+        /* 36 */ "more_keys_for_cyrillic_o",
+        /* 37 */ "more_keys_for_cyrillic_soft_sign",
+        /* 38 */ "more_keys_for_east_slavic_row2_11",
+        /* 39 */ "keylabel_for_south_slavic_row1_6",
+        /* 40 */ "keylabel_for_south_slavic_row2_11",
+        /* 41 */ "keylabel_for_south_slavic_row3_1",
+        /* 42 */ "keylabel_for_south_slavic_row3_8",
+        /* 43 */ "more_keys_for_cyrillic_ie",
+        /* 44 */ "more_keys_for_cyrillic_i",
+        /* 45 */ "label_to_alpha_key",
+        /* 46 */ "single_quotes",
+        /* 47 */ "double_quotes",
+        /* 48 */ "single_angle_quotes",
+        /* 49 */ "double_angle_quotes",
+        /* 50 */ "more_keys_for_currency_dollar",
+        /* 51 */ "keylabel_for_currency_generic",
+        /* 52 */ "more_keys_for_currency_generic",
+        /* 53 */ "more_keys_for_punctuation",
+        /* 54 */ "more_keys_for_star",
+        /* 55 */ "more_keys_for_bullet",
+        /* 56 */ "more_keys_for_plus",
+        /* 57 */ "more_keys_for_left_parenthesis",
+        /* 58 */ "more_keys_for_right_parenthesis",
+        /* 59 */ "more_keys_for_less_than",
+        /* 60 */ "more_keys_for_greater_than",
+        /* 61 */ "more_keys_for_arabic_diacritics",
+        /* 62 */ "keyhintlabel_for_arabic_diacritics",
+        /* 63 */ "keylabel_for_symbols_1",
+        /* 64 */ "keylabel_for_symbols_2",
+        /* 65 */ "keylabel_for_symbols_3",
+        /* 66 */ "keylabel_for_symbols_4",
+        /* 67 */ "keylabel_for_symbols_5",
+        /* 68 */ "keylabel_for_symbols_6",
+        /* 69 */ "keylabel_for_symbols_7",
+        /* 70 */ "keylabel_for_symbols_8",
+        /* 71 */ "keylabel_for_symbols_9",
+        /* 72 */ "keylabel_for_symbols_0",
+        /* 73 */ "label_to_symbol_key",
+        /* 74 */ "label_to_symbol_with_microphone_key",
+        /* 75 */ "additional_more_keys_for_symbols_1",
+        /* 76 */ "additional_more_keys_for_symbols_2",
+        /* 77 */ "additional_more_keys_for_symbols_3",
+        /* 78 */ "additional_more_keys_for_symbols_4",
+        /* 79 */ "additional_more_keys_for_symbols_5",
+        /* 80 */ "additional_more_keys_for_symbols_6",
+        /* 81 */ "additional_more_keys_for_symbols_7",
+        /* 82 */ "additional_more_keys_for_symbols_8",
+        /* 83 */ "additional_more_keys_for_symbols_9",
+        /* 84 */ "additional_more_keys_for_symbols_0",
+        /* 85 */ "more_keys_for_symbols_1",
+        /* 86 */ "more_keys_for_symbols_2",
+        /* 87 */ "more_keys_for_symbols_3",
+        /* 88 */ "more_keys_for_symbols_4",
+        /* 89 */ "more_keys_for_symbols_5",
+        /* 90 */ "more_keys_for_symbols_6",
+        /* 91 */ "more_keys_for_symbols_7",
+        /* 92 */ "more_keys_for_symbols_8",
+        /* 93 */ "more_keys_for_symbols_9",
+        /* 94 */ "more_keys_for_symbols_0",
+        /* 95 */ "keylabel_for_comma",
+        /* 96 */ "more_keys_for_comma",
+        /* 97 */ "keylabel_for_symbols_question",
+        /* 98 */ "keylabel_for_symbols_semicolon",
+        /* 99 */ "keylabel_for_symbols_percent",
+        /* 100 */ "more_keys_for_symbols_exclamation",
+        /* 101 */ "more_keys_for_symbols_question",
+        /* 102 */ "more_keys_for_symbols_semicolon",
+        /* 103 */ "more_keys_for_symbols_percent",
+        /* 104 */ "keylabel_for_tablet_comma",
+        /* 105 */ "keyhintlabel_for_tablet_comma",
+        /* 106 */ "more_keys_for_tablet_comma",
+        /* 107 */ "keyhintlabel_for_tablet_period",
+        /* 108 */ "more_keys_for_tablet_period",
+        /* 109 */ "keylabel_for_apostrophe",
+        /* 110 */ "keyhintlabel_for_apostrophe",
+        /* 111 */ "more_keys_for_apostrophe",
+        /* 112 */ "more_keys_for_q",
+        /* 113 */ "more_keys_for_x",
+        /* 114 */ "keylabel_for_q",
+        /* 115 */ "keylabel_for_w",
+        /* 116 */ "keylabel_for_y",
+        /* 117 */ "keylabel_for_x",
+        /* 118 */ "keylabel_for_spanish_row2_10",
+        /* 119 */ "more_keys_for_am_pm",
+        /* 120 */ "settings_as_more_key",
+        /* 121 */ "shortcut_as_more_key",
+        /* 122 */ "action_next_as_more_key",
+        /* 123 */ "action_previous_as_more_key",
+        /* 124 */ "label_to_more_symbol_key",
+        /* 125 */ "label_to_more_symbol_for_tablet_key",
+        /* 126 */ "label_tab_key",
+        /* 127 */ "label_to_phone_numeric_key",
+        /* 128 */ "label_to_phone_symbols_key",
+        /* 129 */ "label_time_am",
+        /* 130 */ "label_time_pm",
+        /* 131 */ "label_to_symbol_key_pcqwerty",
+        /* 132 */ "keylabel_for_popular_domain",
+        /* 133 */ "more_keys_for_popular_domain",
+        /* 134 */ "more_keys_for_smiley",
+        /* 135 */ "single_laqm_raqm",
+        /* 136 */ "single_laqm_raqm_rtl",
+        /* 137 */ "single_raqm_laqm",
+        /* 138 */ "double_laqm_raqm",
+        /* 139 */ "double_laqm_raqm_rtl",
+        /* 140 */ "double_raqm_laqm",
+        /* 141 */ "single_lqm_rqm",
+        /* 142 */ "single_9qm_lqm",
+        /* 143 */ "single_9qm_rqm",
+        /* 144 */ "double_lqm_rqm",
+        /* 145 */ "double_9qm_lqm",
+        /* 146 */ "double_9qm_rqm",
+        /* 147 */ "more_keys_for_single_quote",
+        /* 148 */ "more_keys_for_double_quote",
+        /* 149 */ "more_keys_for_tablet_double_quote",
     };
 
     private static final String EMPTY = "";
@@ -259,147 +262,147 @@
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        EMPTY, EMPTY, EMPTY,
-        /* ~41 */
+        EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
-        /* 42 */ "ABC",
-        /* 43 */ "!text/single_lqm_rqm",
-        /* 44 */ "!text/double_lqm_rqm",
-        /* 45 */ "!text/single_laqm_raqm",
-        /* 46 */ "!text/double_laqm_raqm",
+        /* 45 */ "ABC",
+        /* 46 */ "!text/single_lqm_rqm",
+        /* 47 */ "!text/double_lqm_rqm",
+        /* 48 */ "!text/single_laqm_raqm",
+        /* 49 */ "!text/double_laqm_raqm",
         // U+00A2: "¢" CENT SIGN
         // U+00A3: "£" POUND SIGN
         // U+20AC: "€" EURO SIGN
         // U+00A5: "¥" YEN SIGN
         // U+20B1: "₱" PESO SIGN
-        /* 47 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
-        /* 48 */ "$",
-        /* 49 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 50 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+        /* 51 */ "$",
+        /* 52 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
         // U+2020: "†" DAGGER
         // U+2021: "‡" DOUBLE DAGGER
         // U+2605: "★" BLACK STAR
-        /* 51 */ "\u2020,\u2021,\u2605",
+        /* 54 */ "\u2020,\u2021,\u2605",
         // U+266A: "♪" EIGHTH NOTE
         // U+2665: "♥" BLACK HEART SUIT
         // U+2660: "♠" BLACK SPADE SUIT
         // U+2666: "♦" BLACK DIAMOND SUIT
         // U+2663: "♣" BLACK CLUB SUIT
-        /* 52 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+        /* 55 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
         // U+00B1: "±" PLUS-MINUS SIGN
-        /* 53 */ "\u00B1",
+        /* 56 */ "\u00B1",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 54 */ "!fixedColumnOrder!3,<,{,[",
-        /* 55 */ "!fixedColumnOrder!3,>,},]",
+        /* 57 */ "!fixedColumnOrder!3,<,{,[",
+        /* 58 */ "!fixedColumnOrder!3,>,},]",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
-        /* 58 */ EMPTY,
-        /* 59 */ EMPTY,
-        /* 60 */ "1",
-        /* 61 */ "2",
-        /* 62 */ "3",
-        /* 63 */ "4",
-        /* 64 */ "5",
-        /* 65 */ "6",
-        /* 66 */ "7",
-        /* 67 */ "8",
-        /* 68 */ "9",
-        /* 69 */ "0",
+        /* 59 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
+        /* 61 */ EMPTY,
+        /* 62 */ EMPTY,
+        /* 63 */ "1",
+        /* 64 */ "2",
+        /* 65 */ "3",
+        /* 66 */ "4",
+        /* 67 */ "5",
+        /* 68 */ "6",
+        /* 69 */ "7",
+        /* 70 */ "8",
+        /* 71 */ "9",
+        /* 72 */ "0",
         // Label for "switch to symbols" key.
-        /* 70 */ "?123",
+        /* 73 */ "?123",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "123",
-        /* 72~ */
+        /* 74 */ "123",
+        /* 75~ */
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        /* ~81 */
+        /* ~84 */
         // U+00B9: "¹" SUPERSCRIPT ONE
         // U+00BD: "½" VULGAR FRACTION ONE HALF
         // U+2153: "⅓" VULGAR FRACTION ONE THIRD
         // U+00BC: "¼" VULGAR FRACTION ONE QUARTER
         // U+215B: "⅛" VULGAR FRACTION ONE EIGHTH
-        /* 82 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+        /* 85 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
         // U+00B2: "²" SUPERSCRIPT TWO
         // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
-        /* 83 */ "\u00B2,\u2154",
+        /* 86 */ "\u00B2,\u2154",
         // U+00B3: "³" SUPERSCRIPT THREE
         // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
         // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
-        /* 84 */ "\u00B3,\u00BE,\u215C",
+        /* 87 */ "\u00B3,\u00BE,\u215C",
         // U+2074: "⁴" SUPERSCRIPT FOUR
-        /* 85 */ "\u2074",
+        /* 88 */ "\u2074",
         // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
-        /* 86 */ "\u215D",
-        /* 87 */ EMPTY,
-        // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
-        /* 88 */ "\u215E",
-        /* 89 */ EMPTY,
+        /* 89 */ "\u215D",
         /* 90 */ EMPTY,
+        // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
+        /* 91 */ "\u215E",
+        /* 92 */ EMPTY,
+        /* 93 */ EMPTY,
         // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
         // U+2205: "∅" EMPTY SET
-        /* 91 */ "\u207F,\u2205",
-        /* 92 */ ",",
-        /* 93 */ EMPTY,
-        /* 94 */ "?",
-        /* 95 */ ";",
-        /* 96 */ "%",
+        /* 94 */ "\u207F,\u2205",
+        /* 95 */ ",",
+        /* 96 */ EMPTY,
+        /* 97 */ "?",
+        /* 98 */ ";",
+        /* 99 */ "%",
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 97 */ "\u00A1",
+        /* 100 */ "\u00A1",
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 98 */ "\u00BF",
-        /* 99 */ EMPTY,
+        /* 101 */ "\u00BF",
+        /* 102 */ EMPTY,
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\u2030",
-        /* 101 */ ",",
-        /* 102 */ "!",
-        /* 103 */ "!",
-        /* 104 */ "?",
-        /* 105 */ "?",
-        /* 106 */ "\'",
-        /* 107 */ "\"",
-        /* 108 */ "\"",
-        /* 109 */ EMPTY,
-        /* 110 */ EMPTY,
-        /* 111 */ "q",
-        /* 112 */ "w",
-        /* 113 */ "y",
-        /* 114 */ "x",
+        /* 103 */ "\u2030",
+        /* 104 */ ",",
+        /* 105 */ "!",
+        /* 106 */ "!",
+        /* 107 */ "?",
+        /* 108 */ "?",
+        /* 109 */ "\'",
+        /* 110 */ "\"",
+        /* 111 */ "\"",
+        /* 112 */ EMPTY,
+        /* 113 */ EMPTY,
+        /* 114 */ "q",
+        /* 115 */ "w",
+        /* 116 */ "y",
+        /* 117 */ "x",
         // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-        /* 115 */ "\u00F1",
-        /* 116 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
-        /* 117 */ "!icon/settings_key|!code/key_settings",
-        /* 118 */ "!icon/shortcut_key|!code/key_shortcut",
-        /* 119 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
-        /* 120 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+        /* 118 */ "\u00F1",
+        /* 119 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
+        /* 120 */ "!icon/settings_key|!code/key_settings",
+        /* 121 */ "!icon/shortcut_key|!code/key_shortcut",
+        /* 122 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
+        /* 123 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
-        /* 121 */ "= \\ <",
+        /* 124 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 122 */ "~ \\ {",
+        /* 125 */ "~ \\ {",
         // Label for "Tab" key.  Must be short to fit on key!
-        /* 123 */ "Tab",
+        /* 126 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
-        /* 124 */ "123",
+        /* 127 */ "123",
         // Label for "switch to phone symbols" key.  Must be short to fit on key!
         // U+FF0A: "*" FULLWIDTH ASTERISK
         // U+FF03: "#" FULLWIDTH NUMBER SIGN
-        /* 125 */ "\uFF0A\uFF03",
+        /* 128 */ "\uFF0A\uFF03",
         // Key label for "ante meridiem"
-        /* 126 */ "AM",
+        /* 129 */ "AM",
         // Key label for "post meridiem"
-        /* 127 */ "PM",
+        /* 130 */ "PM",
         // Label for "switch to symbols" key on PC QWERTY layout
-        /* 128 */ "Sym",
-        /* 129 */ ".com",
+        /* 131 */ "Sym",
+        /* 132 */ ".com",
         // popular web domains for the locale - most popular, displayed on the keyboard
-        /* 130 */ "!hasLabels!,.net,.org,.gov,.edu",
-        /* 131 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+        /* 133 */ "!hasLabels!,.net,.org,.gov,.edu",
+        /* 134 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
@@ -421,24 +424,24 @@
         // The following each quotation mark pair consist of
         // <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 132 */ "\u2039,\u203A",
-        /* 133 */ "\u2039|\u203A,\u203A|\u2039",
-        /* 134 */ "\u203A,\u2039",
-        /* 135 */ "\u00AB,\u00BB",
-        /* 136 */ "\u00AB|\u00BB,\u00BB|\u00AB",
-        /* 137 */ "\u00BB,\u00AB",
+        /* 135 */ "\u2039,\u203A",
+        /* 136 */ "\u2039|\u203A,\u203A|\u2039",
+        /* 137 */ "\u203A,\u2039",
+        /* 138 */ "\u00AB,\u00BB",
+        /* 139 */ "\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 140 */ "\u00BB,\u00AB",
         // The following each quotation mark triplet consists of
         // <another quotation mark>, <opening quotation mark>, <closing quotation mark>
         // and is named after (single|double)_<opening quotation mark>_<closing quotation mark>.
-        /* 138 */ "\u201A,\u2018,\u2019",
-        /* 139 */ "\u2019,\u201A,\u2018",
-        /* 140 */ "\u2018,\u201A,\u2019",
-        /* 141 */ "\u201E,\u201C,\u201D",
-        /* 142 */ "\u201D,\u201E,\u201C",
-        /* 143 */ "\u201C,\u201E,\u201D",
-        /* 144 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
-        /* 145 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
-        /* 146 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
+        /* 141 */ "\u201A,\u2018,\u2019",
+        /* 142 */ "\u2019,\u201A,\u2018",
+        /* 143 */ "\u2018,\u201A,\u2019",
+        /* 144 */ "\u201E,\u201C,\u201D",
+        /* 145 */ "\u201D,\u201E,\u201C",
+        /* 146 */ "\u201C,\u201E,\u201D",
+        /* 147 */ "!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes",
+        /* 148 */ "!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes",
+        /* 149 */ "!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes",
     };
 
     /* Language af: Afrikaans */
@@ -499,45 +502,45 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0623: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+062C: "پ" ARABIC LETTER PEH
-        /* 42 */ "\u0623\u200C\u0628\u200C\u062C",
-        /* 43 */ null,
-        /* 44 */ null,
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 45 */ "\u0623\u200C\u0628\u200C\u062C",
+        /* 46 */ null,
+        /* 47 */ null,
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null,
-        /* ~49 */
+        /* ~52 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 51 */ "\u2605,\u066D",
+        /* 54 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 52 */ "\u266A",
-        /* 53 */ null,
+        /* 55 */ "\u266A",
+        /* 56 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0654: "ٔ" ARABIC HAMZA ABOVE
         // U+0652: "ْ" ARABIC SUKUN
@@ -554,70 +557,115 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 58 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 59 */ "\u0651",
+        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0654|\u0654, \u0652|\u0652, \u064D|\u064D, \u064C|\u064C, \u064B|\u064B, \u0651|\u0651, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u0650|\u0650, \u064F|\u064F, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 62 */ "\u0651",
         // U+0661: "١" ARABIC-INDIC DIGIT ONE
-        /* 60 */ "\u0661",
+        /* 63 */ "\u0661",
         // U+0662: "٢" ARABIC-INDIC DIGIT TWO
-        /* 61 */ "\u0662",
+        /* 64 */ "\u0662",
         // U+0663: "٣" ARABIC-INDIC DIGIT THREE
-        /* 62 */ "\u0663",
+        /* 65 */ "\u0663",
         // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
-        /* 63 */ "\u0664",
+        /* 66 */ "\u0664",
         // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
-        /* 64 */ "\u0665",
+        /* 67 */ "\u0665",
         // U+0666: "٦" ARABIC-INDIC DIGIT SIX
-        /* 65 */ "\u0666",
+        /* 68 */ "\u0666",
         // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
-        /* 66 */ "\u0667",
+        /* 69 */ "\u0667",
         // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
-        /* 67 */ "\u0668",
+        /* 70 */ "\u0668",
         // U+0669: "٩" ARABIC-INDIC DIGIT NINE
-        /* 68 */ "\u0669",
+        /* 71 */ "\u0669",
         // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
-        /* 69 */ "\u0660",
+        /* 72 */ "\u0660",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 70 */ "\u0663\u0662\u0661\u061F",
+        /* 73 */ "\u0663\u0662\u0661\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u0663\u0662\u0661",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
+        /* 74 */ "\u0663\u0662\u0661",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 81 */ "0,\u066B,\u066C",
-        /* 82~ */
+        /* 84 */ "0,\u066B,\u066C",
+        /* 85~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~91 */
+        /* ~94 */
         // U+060C: "،" ARABIC COMMA
-        /* 92 */ "\u060C",
-        /* 93 */ "\\,",
-        /* 94 */ "\u061F",
-        /* 95 */ "\u061B",
+        /* 95 */ "\u060C",
+        /* 96 */ "\\,",
+        /* 97 */ "\u061F",
+        /* 98 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 96 */ "\u066A",
-        /* 97 */ null,
-        /* 98 */ "?",
-        /* 99 */ ";",
+        /* 99 */ "\u066A",
+        /* 100 */ null,
+        /* 101 */ "?",
+        /* 102 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\\%,\u2030",
-        /* 101~ */
+        /* 103 */ "\\%,\u2030",
+        /* 104~ */
         null, null, null, null, null,
-        /* ~105 */
+        /* ~108 */
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 106 */ "\u060C",
-        /* 107 */ "\u061F",
-        /* 108 */ "\u061F,\u061B,!,:,-,/,\',\"",
+        /* 109 */ "\u060C",
+        /* 110 */ "\u061F",
+        /* 111 */ "\u061F,\u061B,!,:,-,/,\',\"",
+    };
+
+    /* Language az: Azerbaijani */
+    private static final String[] LANGUAGE_az = {
+        /* 0 */ null,
+        // U+0259: "ə" LATIN SMALL LETTER SCHWA
+        /* 1 */ "\u0259",
+        // U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+        // U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+        // U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+        // U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+        // U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+        // U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+        // U+012B: "ī" LATIN SMALL LETTER I WITH MACRON
+        /* 2 */ "\u0131,\u00EE,\u00EF,\u00EC,\u00ED,\u012F,\u012B",
+        // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+        // U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+        // U+0153: "œ" LATIN SMALL LIGATURE OE
+        // U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+        // U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+        // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+        // U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+        // U+014D: "ō" LATIN SMALL LETTER O WITH MACRON
+        /* 3 */ "\u00F6,\u00F4,\u0153,\u00F2,\u00F3,\u00F5,\u00F8,\u014D",
+        // U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+        // U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+        // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+        // U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+        // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
+        /* 4 */ "\u00FC,\u00FB,\u00F9,\u00FA,\u016B",
+        // U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+        // U+00DF: "ß" LATIN SMALL LETTER SHARP S
+        // U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+        // U+0161: "š" LATIN SMALL LETTER S WITH CARON
+        /* 5 */ "\u015F,\u00DF,\u015B,\u0161",
+        /* 6 */ null,
+        // U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+        // U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+        // U+010D: "č" LATIN SMALL LETTER C WITH CARON
+        /* 7 */ "\u00E7,\u0107,\u010D",
+        /* 8~ */
+        null, null, null, null, null, null, null,
+        /* ~14 */
+        // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
+        /* 15 */ "\u011F",
     };
 
     /* Language be: Belarusian */
@@ -637,23 +685,23 @@
         // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
         /* 29 */ "\u0456",
         /* 30~ */
-        null, null, null, null, null,
-        /* ~34 */
+        null, null, null, null, null, null, null,
+        /* ~36 */
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language bg: Bulgarian */
@@ -661,16 +709,16 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ null,
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ null,
         // single_quotes of Bulgarian is default single_quotes_right_left.
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language ca: Catalan */
@@ -804,11 +852,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language da: Danish */
@@ -872,12 +921,12 @@
         /* 24 */ "\u00F6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language de: German */
@@ -923,12 +972,12 @@
         /* 7~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language el: Greek */
@@ -936,13 +985,13 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0391: "Α" GREEK CAPITAL LETTER ALPHA
         // U+0392: "Β" GREEK CAPITAL LETTER BETA
         // U+0393: "Γ" GREEK CAPITAL LETTER GAMMA
-        /* 42 */ "\u0391\u0392\u0393",
+        /* 45 */ "\u0391\u0392\u0393",
     };
 
     /* Language en: English */
@@ -1113,20 +1162,21 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~108 */
-        /* 109 */ "q",
-        /* 110 */ "x",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null,
+        /* ~111 */
+        /* 112 */ "q",
+        /* 113 */ "x",
         // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
-        /* 111 */ "\u015D",
+        /* 114 */ "\u015D",
         // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
-        /* 112 */ "\u011D",
+        /* 115 */ "\u011D",
         // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
-        /* 113 */ "\u016D",
+        /* 116 */ "\u016D",
         // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
-        /* 114 */ "\u0109",
+        /* 117 */ "\u0109",
         // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
-        /* 115 */ "\u0135",
+        /* 118 */ "\u0135",
     };
 
     /* Language es: Spanish */
@@ -1184,25 +1234,25 @@
         /* 8~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~49 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~52 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 50 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
-        /* 51~ */
+        /* 53 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
+        /* 54~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null,
-        /* ~102 */
+        /* ~105 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 103 */ "!,\u00A1",
-        /* 104 */ null,
+        /* 106 */ "!,\u00A1",
+        /* 107 */ null,
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 105 */ "?,\u00BF",
-        /* 106 */ "\"",
-        /* 107 */ "\'",
-        /* 108 */ "\'",
+        /* 108 */ "?,\u00BF",
+        /* 109 */ "\"",
+        /* 110 */ "\'",
+        /* 111 */ "\'",
     };
 
     /* Language et: Estonian */
@@ -1305,10 +1355,10 @@
         /* 23 */ "\u00F5",
         /* 24~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language fa: Persian */
@@ -1316,45 +1366,45 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0627: "ا" ARABIC LETTER ALEF
         // U+200C: ZERO WIDTH NON-JOINER
         // U+0628: "ب" ARABIC LETTER BEH
         // U+067E: "پ" ARABIC LETTER PEH
-        /* 42 */ "\u0627\u200C\u0628\u200C\u067E",
-        /* 43 */ null,
-        /* 44 */ null,
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 45 */ "\u0627\u200C\u0628\u200C\u067E",
+        /* 46 */ null,
+        /* 47 */ null,
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null,
-        /* ~49 */
+        /* ~52 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 53 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 51 */ "\u2605,\u066D",
+        /* 54 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 52 */ "\u266A",
-        /* 53 */ null,
+        /* 55 */ "\u266A",
+        /* 56 */ null,
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
         // U+FD3E: "﴾" ORNATE LEFT PARENTHESIS
         // U+FD3F: "﴿" ORNATE RIGHT PARENTHESIS
-        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0652: "ْ" ARABIC SUKUN
         // U+0651: "ّ" ARABIC SHADDA
@@ -1371,74 +1421,74 @@
         // U+0640: "ـ" ARABIC TATWEEL
         // In order to make Tatweel easily distinguishable from other punctuations, we use consecutive Tatweels only for its displayed label.
         // Note: The space character is needed as a preceding letter to draw Arabic diacritics characters correctly.
-        /* 58 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
-        /* 59 */ "\u064B",
+        /* 61 */ "!fixedColumnOrder!7, \u0655|\u0655, \u0652|\u0652, \u0651|\u0651, \u064C|\u064C, \u064D|\u064D, \u064B|\u064B, \u0654|\u0654, \u0656|\u0656, \u0670|\u0670, \u0653|\u0653, \u064F|\u064F, \u0650|\u0650, \u064E|\u064E,\u0640\u0640\u0640|\u0640",
+        /* 62 */ "\u064B",
         // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
-        /* 60 */ "\u06F1",
+        /* 63 */ "\u06F1",
         // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
-        /* 61 */ "\u06F2",
+        /* 64 */ "\u06F2",
         // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
-        /* 62 */ "\u06F3",
+        /* 65 */ "\u06F3",
         // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
-        /* 63 */ "\u06F4",
+        /* 66 */ "\u06F4",
         // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
-        /* 64 */ "\u06F5",
+        /* 67 */ "\u06F5",
         // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
-        /* 65 */ "\u06F6",
+        /* 68 */ "\u06F6",
         // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
-        /* 66 */ "\u06F7",
+        /* 69 */ "\u06F7",
         // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
-        /* 67 */ "\u06F8",
+        /* 70 */ "\u06F8",
         // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
-        /* 68 */ "\u06F9",
+        /* 71 */ "\u06F9",
         // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
-        /* 69 */ "\u06F0",
+        /* 72 */ "\u06F0",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 70 */ "\u06F3\u06F2\u06F1\u061F",
+        /* 73 */ "\u06F3\u06F2\u06F1\u061F",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u06F3\u06F2\u06F1",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
+        /* 74 */ "\u06F3\u06F2\u06F1",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 81 */ "0,\u066B,\u066C",
-        /* 82~ */
+        /* 84 */ "0,\u066B,\u066C",
+        /* 85~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~91 */
+        /* ~94 */
         // U+060C: "،" ARABIC COMMA
-        /* 92 */ "\u060C",
-        /* 93 */ "\\,",
-        /* 94 */ "\u061F",
-        /* 95 */ "\u061B",
+        /* 95 */ "\u060C",
+        /* 96 */ "\\,",
+        /* 97 */ "\u061F",
+        /* 98 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 96 */ "\u066A",
-        /* 97 */ null,
-        /* 98 */ "?",
-        /* 99 */ ";",
+        /* 99 */ "\u066A",
+        /* 100 */ null,
+        /* 101 */ "?",
+        /* 102 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 100 */ "\\%,\u2030",
+        /* 103 */ "\\%,\u2030",
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-        /* 101 */ "\u060C",
-        /* 102 */ "!",
-        /* 103 */ "!,\\,",
-        /* 104 */ "\u061F",
-        /* 105 */ "\u061F,?",
-        /* 106 */ "\u060C",
+        /* 104 */ "\u060C",
+        /* 105 */ "!",
+        /* 106 */ "!,\\,",
         /* 107 */ "\u061F",
-        /* 108 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 108 */ "\u061F,?",
+        /* 109 */ "\u060C",
+        /* 110 */ "\u061F",
+        /* 111 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
     };
 
     /* Language fi: Finnish */
@@ -1546,56 +1596,56 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0915: "क" DEVANAGARI LETTER KA
         // U+0916: "ख" DEVANAGARI LETTER KHA
         // U+0917: "ग" DEVANAGARI LETTER GA
-        /* 42 */ "\u0915\u0916\u0917",
-        /* 43~ */
+        /* 45 */ "\u0915\u0916\u0917",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20B9: "₹" INDIAN RUPEE SIGN
-        /* 48 */ "\u20B9",
-        /* 49~ */
+        /* 51 */ "\u20B9",
+        /* 52~ */
         null, null, null, null, null, null, null, null, null, null, null,
-        /* ~59 */
+        /* ~62 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
-        /* 60 */ "\u0967",
+        /* 63 */ "\u0967",
         // U+0968: "२" DEVANAGARI DIGIT TWO
-        /* 61 */ "\u0968",
+        /* 64 */ "\u0968",
         // U+0969: "३" DEVANAGARI DIGIT THREE
-        /* 62 */ "\u0969",
+        /* 65 */ "\u0969",
         // U+096A: "४" DEVANAGARI DIGIT FOUR
-        /* 63 */ "\u096A",
+        /* 66 */ "\u096A",
         // U+096B: "५" DEVANAGARI DIGIT FIVE
-        /* 64 */ "\u096B",
+        /* 67 */ "\u096B",
         // U+096C: "६" DEVANAGARI DIGIT SIX
-        /* 65 */ "\u096C",
+        /* 68 */ "\u096C",
         // U+096D: "७" DEVANAGARI DIGIT SEVEN
-        /* 66 */ "\u096D",
+        /* 69 */ "\u096D",
         // U+096E: "८" DEVANAGARI DIGIT EIGHT
-        /* 67 */ "\u096E",
+        /* 70 */ "\u096E",
         // U+096F: "९" DEVANAGARI DIGIT NINE
-        /* 68 */ "\u096F",
+        /* 71 */ "\u096F",
         // U+0966: "०" DEVANAGARI DIGIT ZERO
-        /* 69 */ "\u0966",
+        /* 72 */ "\u0966",
         // Label for "switch to symbols" key.
-        /* 70 */ "?\u0967\u0968\u0969",
+        /* 73 */ "?\u0967\u0968\u0969",
         // Label for "switch to symbols with microphone" key. This string shouldn't include the "mic"
         // part because it'll be appended by the code.
-        /* 71 */ "\u0967\u0968\u0969",
-        /* 72 */ "1",
-        /* 73 */ "2",
-        /* 74 */ "3",
-        /* 75 */ "4",
-        /* 76 */ "5",
-        /* 77 */ "6",
-        /* 78 */ "7",
-        /* 79 */ "8",
-        /* 80 */ "9",
-        /* 81 */ "0",
+        /* 74 */ "\u0967\u0968\u0969",
+        /* 75 */ "1",
+        /* 76 */ "2",
+        /* 77 */ "3",
+        /* 78 */ "4",
+        /* 79 */ "5",
+        /* 80 */ "6",
+        /* 81 */ "7",
+        /* 82 */ "8",
+        /* 83 */ "9",
+        /* 84 */ "0",
     };
 
     /* Language hr: Croatian */
@@ -1626,11 +1676,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language hu: Hungarian */
@@ -1679,12 +1730,12 @@
         /* 5~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language is: Icelandic */
@@ -1750,10 +1801,10 @@
         /* 22 */ "\u00FE",
         /* 23~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language it: Italian */
@@ -1806,13 +1857,13 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+05D0: "א" HEBREW LETTER ALEF
         // U+05D1: "ב" HEBREW LETTER BET
         // U+05D2: "ג" HEBREW LETTER GIMEL
-        /* 42 */ "\u05D0\u05D1\u05D2",
+        /* 45 */ "\u05D0\u05D1\u05D2",
         // The following characters don't need BIDI mirroring.
         // U+2018: "‘" LEFT SINGLE QUOTATION MARK
         // U+2019: "’" RIGHT SINGLE QUOTATION MARK
@@ -1820,31 +1871,31 @@
         // U+201C: "“" LEFT DOUBLE QUOTATION MARK
         // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
         // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        /* 43 */ "\u2018,\u2019,\u201A",
-        /* 44 */ "\u201C,\u201D,\u201E",
-        /* 45 */ "!text/single_laqm_raqm_rtl",
-        /* 46 */ "!text/double_laqm_raqm_rtl",
-        /* 47~ */
+        /* 46 */ "\u2018,\u2019,\u201A",
+        /* 47 */ "\u201C,\u201D,\u201E",
+        /* 48 */ "!text/single_laqm_raqm_rtl",
+        /* 49 */ "!text/double_laqm_raqm_rtl",
+        /* 50~ */
         null, null, null, null,
-        /* ~50 */
+        /* ~53 */
         // U+2605: "★" BLACK STAR
-        /* 51 */ "\u2605",
-        /* 52 */ null,
+        /* 54 */ "\u2605",
+        /* 55 */ null,
         // U+00B1: "±" PLUS-MINUS SIGN
         // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
-        /* 53 */ "\u00B1,\uFB29",
+        /* 56 */ "\u00B1,\uFB29",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 54 */ "!fixedColumnOrder!3,<|>,{|},[|]",
-        /* 55 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+        /* 57 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+        /* 58 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
         // U+2264: "≤" LESS-THAN OR EQUAL TO
         // U+2265: "≥" GREATER-THAN EQUAL TO
         // U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
         // U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
         // U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 59 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 60 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
     };
 
     /* Language ka: Georgian */
@@ -1852,15 +1903,63 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+10D0: "ა" GEORGIAN LETTER AN
         // U+10D1: "ბ" GEORGIAN LETTER BAN
         // U+10D2: "გ" GEORGIAN LETTER GAN
-        /* 42 */ "\u10D0\u10D1\u10D2",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u10D0\u10D1\u10D2",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+    };
+
+    /* Language kk: Kazakh */
+    private static final String[] LANGUAGE_kk = {
+        /* 0~ */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null, null, null, null, null, null, null, null, null, null,
+        /* ~24 */
+        // U+0449: "щ" CYRILLIC SMALL LETTER SHCHA
+        /* 25 */ "\u0449",
+        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+        /* 26 */ "\u044A",
+        // U+044B: "ы" CYRILLIC SMALL LETTER YERU
+        /* 27 */ "\u044B",
+        // U+044D: "э" CYRILLIC SMALL LETTER E
+        /* 28 */ "\u044D",
+        // U+0438: "и" CYRILLIC SMALL LETTER I
+        /* 29 */ "\u0438",
+        // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+        // U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+        /* 30 */ "\u04AF,\u04B1",
+        // U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER
+        /* 31 */ "\u049B",
+        // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
+        /* 32 */ "\u04A3",
+        // U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE
+        /* 33 */ "\u0493",
+        // U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+        /* 34 */ "\u0456",
+        // U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA
+        /* 35 */ "\u04D9",
+        // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
+        /* 36 */ "\u04E9",
+        // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
+        /* 37 */ "\u044A",
+        // U+04BB: "һ" CYRILLIC SMALL LETTER SHHA
+        /* 38 */ "\u04BB",
+        /* 39~ */
+        null, null, null, null,
+        /* ~42 */
+        // U+0451: "ё" CYRILLIC SMALL LETTER IO
+        /* 43 */ "\u0451",
+        /* 44 */ null,
+        // Label for "switch to alphabetic" key.
+        // U+0410: "А" CYRILLIC CAPITAL LETTER A
+        // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
+        // U+0412: "В" CYRILLIC CAPITAL LETTER VE
+        /* 45 */ "\u0410\u0411\u0412",
     };
 
     /* Language ky: Kirghiz */
@@ -1881,25 +1980,27 @@
         /* 29 */ "\u0438",
         // U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
         /* 30 */ "\u04AF",
+        /* 31 */ null,
         // U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER
-        /* 31 */ "\u04A3",
-        /* 32 */ null,
-        /* 33 */ null,
+        /* 32 */ "\u04A3",
+        /* 33~ */
+        null, null, null,
+        /* ~35 */
         // U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O
-        /* 34 */ "\u04E9",
+        /* 36 */ "\u04E9",
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
+        /* 45 */ "\u0410\u0411\u0412",
     };
 
     /* Language lt: Lithuanian */
@@ -1992,10 +2093,10 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language lv: Latvian */
@@ -2087,10 +2188,10 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language mk: Macedonian */
@@ -2098,27 +2199,27 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~35 */
+        null, null, null, null, null, null, null, null, null,
+        /* ~38 */
         // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
-        /* 36 */ "\u0455",
+        /* 39 */ "\u0455",
         // U+045C: "ќ" CYRILLIC SMALL LETTER KJE
-        /* 37 */ "\u045C",
+        /* 40 */ "\u045C",
         // U+0437: "з" CYRILLIC SMALL LETTER ZE
-        /* 38 */ "\u0437",
+        /* 41 */ "\u0437",
         // U+0453: "ѓ" CYRILLIC SMALL LETTER GJE
-        /* 39 */ "\u0453",
+        /* 42 */ "\u0453",
         // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
-        /* 40 */ "\u0450",
+        /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
-        /* 41 */ "\u045D",
+        /* 44 */ "\u045D",
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language mn: Mongolian */
@@ -2126,18 +2227,18 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43~ */
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20AE: "₮" TUGRIK SIGN
-        /* 48 */ "\u20AE",
+        /* 51 */ "\u20AE",
     };
 
     /* Language nb: Norwegian Bokmål */
@@ -2187,10 +2288,10 @@
         /* 24 */ "\u00E4",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language nl: Dutch */
@@ -2245,10 +2346,10 @@
         /* 9~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language pl: Polish */
@@ -2305,10 +2406,11 @@
         /* 14 */ "\u0142",
         /* 15~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language pt: Portuguese */
@@ -2411,10 +2513,10 @@
         /* 12~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_rqm",
-        /* 44 */ "!text/double_9qm_rqm",
+        null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_rqm",
+        /* 47 */ "!text/double_9qm_rqm",
     };
 
     /* Language ru: Russian */
@@ -2434,23 +2536,23 @@
         // U+0438: "и" CYRILLIC SMALL LETTER I
         /* 29 */ "\u0438",
         /* 30~ */
-        null, null, null, null, null,
-        /* ~34 */
+        null, null, null, null, null, null, null,
+        /* ~36 */
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null,
-        /* ~39 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null,
+        /* ~42 */
         // U+0451: "ё" CYRILLIC SMALL LETTER IO
-        /* 40 */ "\u0451",
-        /* 41 */ null,
+        /* 43 */ "\u0451",
+        /* 44 */ null,
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
     };
 
     /* Language sk: Slovak */
@@ -2543,12 +2645,12 @@
         /* 15 */ "\u0123,\u011F",
         /* 16~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sl: Slovenian */
@@ -2572,11 +2674,12 @@
         /* 13~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~42 */
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null,
+        /* ~45 */
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sr: Serbian */
@@ -2584,8 +2687,8 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null,
-        /* ~35 */
+        null, null, null, null, null, null, null, null, null,
+        /* ~38 */
         // TODO: Move these to sr-Latn once we can handle IETF language tag with script name specified.
         // BEGIN: More keys definitions for Serbian (Latin)
         // U+0161: "š" LATIN SMALL LETTER S WITH CARON
@@ -2605,27 +2708,27 @@
         // END: More keys definitions for Serbian (Latin)
         // BEGIN: More keys definitions for Serbian (Cyrillic)
         // U+0437: "з" CYRILLIC SMALL LETTER ZE
-        /* 36 */ "\u0437",
+        /* 39 */ "\u0437",
         // U+045B: "ћ" CYRILLIC SMALL LETTER TSHE
-        /* 37 */ "\u045B",
+        /* 40 */ "\u045B",
         // U+0455: "ѕ" CYRILLIC SMALL LETTER DZE
-        /* 38 */ "\u0455",
+        /* 41 */ "\u0455",
         // U+0452: "ђ" CYRILLIC SMALL LETTER DJE
-        /* 39 */ "\u0452",
+        /* 42 */ "\u0452",
         // U+0450: "ѐ" CYRILLIC SMALL LETTER IE WITH GRAVE
-        /* 40 */ "\u0450",
+        /* 43 */ "\u0450",
         // U+045D: "ѝ" CYRILLIC SMALL LETTER I WITH GRAVE
-        /* 41 */ "\u045D",
+        /* 44 */ "\u045D",
         // END: More keys definitions for Serbian (Cyrillic)
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sv: Swedish */
@@ -2670,10 +2773,10 @@
         /* 24 */ "\u00E6",
         /* 25~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null,
-        /* ~44 */
-        /* 45 */ "!text/single_raqm_laqm",
-        /* 46 */ "!text/double_raqm_laqm",
+        null, null, null, null, null, null, null, null,
+        /* ~47 */
+        /* 48 */ "!text/single_raqm_laqm",
+        /* 49 */ "!text/double_raqm_laqm",
     };
 
     /* Language sw: Swahili */
@@ -2732,18 +2835,18 @@
         /* 0~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~41 */
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0E01: "ก" THAI CHARACTER KO KAI
         // U+0E02: "ข" THAI CHARACTER KHO KHAI
         // U+0E04: "ค" THAI CHARACTER KHO KHWAI
-        /* 42 */ "\u0E01\u0E02\u0E04",
-        /* 43~ */
+        /* 45 */ "\u0E01\u0E02\u0E04",
+        /* 46~ */
         null, null, null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
-        /* 48 */ "\u0E3F",
+        /* 51 */ "\u0E3F",
     };
 
     /* Language tl: Tagalog */
@@ -2861,30 +2964,32 @@
         /* 28 */ "\u0454",
         // U+0438: "и" CYRILLIC SMALL LETTER I
         /* 29 */ "\u0438",
-        /* 30 */ null,
-        /* 31 */ null,
+        /* 30~ */
+        null, null, null,
+        /* ~32 */
         // U+0491: "ґ" CYRILLIC SMALL LETTER GHE WITH UPTURN
-        /* 32 */ "\u0491",
+        /* 33 */ "\u0491",
         // U+0457: "ї" CYRILLIC SMALL LETTER YI
-        /* 33 */ "\u0457",
-        /* 34 */ null,
+        /* 34 */ "\u0457",
+        /* 35 */ null,
+        /* 36 */ null,
         // U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN
-        /* 35 */ "\u044A",
-        /* 36~ */
-        null, null, null, null, null, null,
-        /* ~41 */
+        /* 37 */ "\u044A",
+        /* 38~ */
+        null, null, null, null, null, null, null,
+        /* ~44 */
         // Label for "switch to alphabetic" key.
         // U+0410: "А" CYRILLIC CAPITAL LETTER A
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
-        /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ "!text/single_9qm_lqm",
-        /* 44 */ "!text/double_9qm_lqm",
-        /* 45~ */
+        /* 45 */ "\u0410\u0411\u0412",
+        /* 46 */ "!text/single_9qm_lqm",
+        /* 47 */ "!text/double_9qm_lqm",
+        /* 48~ */
         null, null, null,
-        /* ~47 */
+        /* ~50 */
         // U+20B4: "₴" HRYVNIA SIGN
-        /* 48 */ "\u20B4",
+        /* 51 */ "\u20B4",
     };
 
     /* Language vi: Vietnamese */
@@ -2969,10 +3074,10 @@
         /* 10~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null,
-        /* ~47 */
+        null, null, null, null, null, null, null, null, null, null, null,
+        /* ~50 */
         // U+20AB: "₫" DONG SIGN
-        /* 48 */ "\u20AB",
+        /* 51 */ "\u20AB",
     };
 
     /* Language zu: Zulu */
@@ -3149,6 +3254,7 @@
         "DEFAULT", LANGUAGE_DEFAULT, /* default */
         "af", LANGUAGE_af, /* Afrikaans */
         "ar", LANGUAGE_ar, /* Arabic */
+        "az", LANGUAGE_az, /* Azerbaijani */
         "be", LANGUAGE_be, /* Belarusian */
         "bg", LANGUAGE_bg, /* Bulgarian */
         "ca", LANGUAGE_ca, /* Catalan */
@@ -3170,6 +3276,7 @@
         "it", LANGUAGE_it, /* Italian */
         "iw", LANGUAGE_iw, /* Hebrew */
         "ka", LANGUAGE_ka, /* Georgian */
+        "kk", LANGUAGE_kk, /* Kazakh */
         "ky", LANGUAGE_ky, /* Kirghiz */
         "lt", LANGUAGE_lt, /* Lithuanian */
         "lv", LANGUAGE_lv, /* Latvian */
diff --git a/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
new file mode 100644
index 0000000..ebbcedc
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/AbstractDictionaryWriter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+// TODO: Quit extending Dictionary after implementing dynamic binary dictionary.
+abstract public class AbstractDictionaryWriter extends Dictionary {
+    /** Used for Log actions from this class */
+    private static final String TAG = AbstractDictionaryWriter.class.getSimpleName();
+
+    private final Context mContext;
+
+    public AbstractDictionaryWriter(final Context context, final String dictType) {
+        super(dictType);
+        mContext = context;
+    }
+
+    abstract public void clear();
+
+    abstract public void addUnigramWord(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord);
+
+    abstract public void addBigramWords(final String word0, final String word1,
+            final int frequency, final boolean isValid);
+
+    abstract public void removeBigramWords(final String word0, final String word1);
+
+    abstract protected void writeBinaryDictionary(final FileOutputStream out)
+            throws IOException, UnsupportedFormatException;
+
+    public void write(final String fileName) {
+        final String tempFileName = fileName + ".temp";
+        final File file = new File(mContext.getFilesDir(), fileName);
+        final File tempFile = new File(mContext.getFilesDir(), tempFileName);
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(tempFile);
+            writeBinaryDictionary(out);
+            out.flush();
+            out.close();
+            tempFile.renameTo(file);
+        } catch (IOException e) {
+            Log.e(TAG, "IO exception while writing file", e);
+        } catch (UnsupportedFormatException e) {
+            Log.e(TAG, "Unsupported format", e);
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 4b5d027..6e26a58 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -308,6 +308,7 @@
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
                 BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
+                Log.e(TAG, "Successfully copied file for wordlist ID " + wordlistId);
                 // Success! Close files (through the finally{} clause) and return.
                 return;
             } catch (Exception e) {
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index 51dc852..31a892e 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -224,14 +224,10 @@
         }
     }
 
-    // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
-    // for this is, since those do not include whitelist entries, the new code with an old version
-    // of the dictionary would lose whitelist functionality.
+    // ## HACK ## we prevent usage of a dictionary before version 18. The reason for this is, since
+    // those do not include whitelist entries, the new code with an old version of the dictionary
+    // would lose whitelist functionality.
     private static boolean hackCanUseDictionaryFile(final Locale locale, final File f) {
-        // Only for English - other languages didn't have a whitelist, hence this
-        // ad-hoc ## HACK ##
-        if (!Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) return true;
-
         FileInputStream inStream = null;
         try {
             // Read the version of the file
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index bb4a42e..ad09b6a 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -149,6 +149,13 @@
     }
 
     /**
+     * Custom request code used in
+     * {@link com.android.inputmethod.keyboard.KeyboardActionListener#onCustomRequest(int)}.
+     */
+    // The code to show input method picker.
+    public static final int CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER = 1;
+
+    /**
      * Some common keys code. Must be positive.
      */
     public static final int CODE_ENTER = '\n';
diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
index 110be9d..c99d0e2 100644
--- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java
@@ -109,7 +109,6 @@
 
     @Override
     public void loadDictionaryAsync() {
-        clearFusionDictionary();
         loadDeviceAccountsEmailAddresses();
         loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI);
         // TODO: Switch this URL to the newer ContactsContract too
@@ -236,6 +235,11 @@
     }
 
     @Override
+    protected boolean needsToReloadBeforeWriting() {
+        return true;
+    }
+
+    @Override
     protected boolean hasContentChanged() {
         final long startTime = SystemClock.uptimeMillis();
         final int contactCount = getContactCount();
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 01ec7f9..5dbc9b1 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -25,7 +25,7 @@
 import android.preference.PreferenceScreen;
 
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 public final class DebugSettings extends PreferenceFragment
         implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -118,7 +118,7 @@
         }
         boolean isDebugMode = mDebugMode.isChecked();
         final String version = getResources().getString(
-                R.string.version_text, Utils.getVersionName(getActivity()));
+                R.string.version_text, ApplicationUtils.getVersionName(getActivity()));
         if (!isDebugMode) {
             mDebugMode.setTitle(version);
             mDebugMode.setSummary("");
diff --git a/java/src/com/android/inputmethod/latin/DictionaryWriter.java b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
new file mode 100644
index 0000000..8be04c1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/DictionaryWriter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.content.Context;
+
+import com.android.inputmethod.keyboard.ProximityInfo;
+import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FormatSpec;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * An in memory dictionary for memorizing entries and writing a binary dictionary.
+ */
+public class DictionaryWriter extends AbstractDictionaryWriter {
+    // TODO: Regenerate version 3 binary dictionary.
+    private static final int BINARY_DICT_VERSION = 2;
+    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
+            new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
+
+    private FusionDictionary mFusionDictionary;
+
+    public DictionaryWriter(final Context context, final String dictType) {
+        super(context, dictType);
+        clear();
+    }
+
+    @Override
+    public void clear() {
+        final HashMap<String, String> attributes = CollectionUtils.newHashMap();
+        mFusionDictionary = new FusionDictionary(new Node(),
+                new FusionDictionary.DictionaryOptions(attributes, false, false));
+    }
+
+    /**
+     * Adds a word unigram to the fusion dictionary.
+     */
+    // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
+    // considering performance regression.
+    @Override
+    public void addUnigramWord(final String word, final String shortcutTarget, final int frequency,
+            final boolean isNotAWord) {
+        if (shortcutTarget == null) {
+            mFusionDictionary.add(word, frequency, null, isNotAWord);
+        } else {
+            // TODO: Do this in the subclass, with this class taking an arraylist.
+            final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
+            shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
+            mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
+        }
+    }
+
+    @Override
+    public void addBigramWords(final String word0, final String word1, final int frequency,
+            final boolean isValid) {
+        mFusionDictionary.setBigram(word0, word1, frequency);
+    }
+
+    @Override
+    public void removeBigramWords(final String word0, final String word1) {
+        // This class don't support removing bigram words.
+    }
+
+    @Override
+    protected void writeBinaryDictionary(final FileOutputStream out)
+            throws IOException, UnsupportedFormatException {
+        BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
+    }
+
+    @Override
+    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
+            final String prevWord, final ProximityInfo proximityInfo,
+            boolean blockOffensiveWords) {
+        // This class doesn't support suggestion.
+        return null;
+    }
+
+    @Override
+    public boolean isValidWord(String word) {
+        // This class doesn't support dictionary retrieval.
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
index 9cdb86c..657fc64 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java
@@ -22,20 +22,12 @@
 
 import com.android.inputmethod.keyboard.ProximityInfo;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
-import com.android.inputmethod.latin.makedict.FormatSpec;
-import com.android.inputmethod.latin.makedict.FusionDictionary;
-import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
-import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
  * Abstract base class for an expandable dictionary that can be created and updated dynamically
@@ -76,8 +68,8 @@
      */
     private BinaryDictionary mBinaryDictionary;
 
-    /** The expandable fusion dictionary used to generate the binary dictionary. */
-    private FusionDictionary mFusionDictionary;
+    /** The in-memory dictionary used to generate the binary dictionary. */
+    private AbstractDictionaryWriter mDictionaryWriter;
 
     /**
      * The name of this dictionary, used as the filename for storing the binary dictionary. Multiple
@@ -92,10 +84,6 @@
     /** Controls access to the local binary dictionary for this instance. */
     private final DictionaryController mLocalDictionaryController = new DictionaryController();
 
-    private static final int BINARY_DICT_VERSION = 1;
-    private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
-            new FormatSpec.FormatOptions(BINARY_DICT_VERSION);
-
     /**
      * Abstract method for loading the unigrams and bigrams of a given dictionary in a background
      * thread.
@@ -137,7 +125,7 @@
         mContext = context;
         mBinaryDictionary = null;
         mSharedDictionaryController = getSharedDictionaryController(filename);
-        clearFusionDictionary();
+        mDictionaryWriter = new DictionaryWriter(context, dictType);
     }
 
     protected static String getFilenameWithLocale(final String name, final String localeStr) {
@@ -150,53 +138,57 @@
     @Override
     public void close() {
         // Ensure that no other threads are accessing the local binary dictionary.
-        mLocalDictionaryController.lock();
+        mLocalDictionaryController.writeLock().lock();
         try {
             if (mBinaryDictionary != null) {
                 mBinaryDictionary.close();
                 mBinaryDictionary = null;
             }
+            mDictionaryWriter.close();
         } finally {
-            mLocalDictionaryController.unlock();
+            mLocalDictionaryController.writeLock().unlock();
         }
     }
 
     /**
-     * Clears the fusion dictionary on the Java side. Note: Does not modify the binary dictionary on
-     * the native side.
+     * Adds a word unigram to the dictionary. Used for loading a dictionary.
      */
-    public void clearFusionDictionary() {
-        final HashMap<String, String> attributes = CollectionUtils.newHashMap();
-        mFusionDictionary = new FusionDictionary(new Node(),
-                new FusionDictionary.DictionaryOptions(attributes, false, false));
+    protected void addWord(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord) {
+        mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
     }
 
     /**
-     * Adds a word unigram to the fusion dictionary. Call updateBinaryDictionary when all changes
-     * are done to update the binary dictionary.
+     * Sets a word bigram in the dictionary. Used for loading a dictionary.
      */
-    // TODO: Create "cache dictionary" to cache fresh words for frequently updated dictionaries,
-    // considering performance regression.
-    protected void addWord(final String word, final String shortcutTarget, final int frequency,
-            final boolean isNotAWord) {
-        if (shortcutTarget == null) {
-            mFusionDictionary.add(word, frequency, null, isNotAWord);
-        } else {
-            // TODO: Do this in the subclass, with this class taking an arraylist.
-            final ArrayList<WeightedString> shortcutTargets = CollectionUtils.newArrayList();
-            shortcutTargets.add(new WeightedString(shortcutTarget, frequency));
-            mFusionDictionary.add(word, frequency, shortcutTargets, isNotAWord);
-        }
-    }
-
-    /**
-     * Sets a word bigram in the fusion dictionary. Call updateBinaryDictionary when all changes are
-     * done to update the binary dictionary.
-     */
-    // TODO: Create "cache dictionary" to cache fresh bigrams for frequently updated dictionaries,
-    // considering performance regression.
     protected void setBigram(final String prevWord, final String word, final int frequency) {
-        mFusionDictionary.setBigram(prevWord, word, frequency);
+        mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+    }
+
+    /**
+     * Dynamically adds a word unigram to the dictionary.
+     */
+    protected void addWordDynamically(final String word, final String shortcutTarget,
+            final int frequency, final boolean isNotAWord) {
+        mLocalDictionaryController.writeLock().lock();
+        try {
+            mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord);
+        } finally {
+            mLocalDictionaryController.writeLock().unlock();
+        }
+    }
+
+    /**
+     * Dynamically sets a word bigram in the dictionary.
+     */
+    protected void setBigramDynamically(final String prevWord, final String word,
+            final int frequency) {
+        mLocalDictionaryController.writeLock().lock();
+        try {
+            mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */);
+        } finally {
+            mLocalDictionaryController.writeLock().unlock();
+        }
     }
 
     @Override
@@ -204,14 +196,29 @@
             final String prevWord, final ProximityInfo proximityInfo,
             final boolean blockOffensiveWords) {
         asyncReloadDictionaryIfRequired();
-        if (mLocalDictionaryController.tryLock()) {
+        // Write lock because getSuggestions in native updates session status.
+        if (mLocalDictionaryController.writeLock().tryLock()) {
             try {
+                final ArrayList<SuggestedWordInfo> inMemDictSuggestion =
+                        mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo,
+                                blockOffensiveWords);
                 if (mBinaryDictionary != null) {
-                    return mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
-                            blockOffensiveWords);
+                    final ArrayList<SuggestedWordInfo> binarySuggestion =
+                            mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo,
+                                    blockOffensiveWords);
+                    if (inMemDictSuggestion == null) {
+                        return binarySuggestion;
+                    } else if (binarySuggestion == null) {
+                        return inMemDictSuggestion;
+                    } else {
+                        binarySuggestion.addAll(binarySuggestion);
+                        return binarySuggestion;
+                    }
+                } else {
+                    return inMemDictSuggestion;
                 }
             } finally {
-                mLocalDictionaryController.unlock();
+                mLocalDictionaryController.writeLock().unlock();
             }
         }
         return null;
@@ -224,11 +231,11 @@
     }
 
     protected boolean isValidWordInner(final String word) {
-        if (mLocalDictionaryController.tryLock()) {
+        if (mLocalDictionaryController.readLock().tryLock()) {
             try {
                 return isValidWordLocked(word);
             } finally {
-                mLocalDictionaryController.unlock();
+                mLocalDictionaryController.readLock().unlock();
             }
         }
         return false;
@@ -239,22 +246,6 @@
         return mBinaryDictionary.isValidWord(word);
     }
 
-    protected boolean isValidBigram(final String word1, final String word2) {
-        if (mBinaryDictionary == null) return false;
-        return mBinaryDictionary.isValidBigram(word1, word2);
-    }
-
-    protected boolean isValidBigramInner(final String word1, final String word2) {
-        if (mLocalDictionaryController.tryLock()) {
-            try {
-                return isValidBigramLocked(word1, word2);
-            } finally {
-                mLocalDictionaryController.unlock();
-            }
-        }
-        return false;
-    }
-
     protected boolean isValidBigramLocked(final String word1, final String word2) {
         if (mBinaryDictionary == null) return false;
         return mBinaryDictionary.isValidBigram(word1, word2);
@@ -273,7 +264,7 @@
      * Loads the current binary dictionary from internal storage. Assumes the dictionary file
      * exists.
      */
-    protected void loadBinaryDictionary() {
+    private void loadBinaryDictionary() {
         if (DEBUG) {
             Log.d(TAG, "Loading binary dictionary: " + mFilename + " request="
                     + mSharedDictionaryController.mLastUpdateRequestTime + " update="
@@ -292,9 +283,12 @@
             // Ensure all threads accessing the current dictionary have finished before swapping in
             // the new one.
             final BinaryDictionary oldBinaryDictionary = mBinaryDictionary;
-            mLocalDictionaryController.lock();
-            mBinaryDictionary = newBinaryDictionary;
-            mLocalDictionaryController.unlock();
+            mLocalDictionaryController.writeLock().lock();
+            try {
+                mBinaryDictionary = newBinaryDictionary;
+            } finally {
+                mLocalDictionaryController.writeLock().unlock();
+            }
             oldBinaryDictionary.close();
         } else {
             mBinaryDictionary = newBinaryDictionary;
@@ -302,6 +296,12 @@
     }
 
     /**
+     * Abstract method for checking if it is required to reload the dictionary before writing
+     * a binary dictionary.
+     */
+    abstract protected boolean needsToReloadBeforeWriting();
+
+    /**
      * Generates and writes a new binary dictionary based on the contents of the fusion dictionary.
      */
     private void generateBinaryDictionary() {
@@ -310,33 +310,11 @@
                     + mSharedDictionaryController.mLastUpdateRequestTime + " update="
                     + mSharedDictionaryController.mLastUpdateTime);
         }
-
-        loadDictionaryAsync();
-
-        final String tempFileName = mFilename + ".temp";
-        final File file = new File(mContext.getFilesDir(), mFilename);
-        final File tempFile = new File(mContext.getFilesDir(), tempFileName);
-        FileOutputStream out = null;
-        try {
-            out = new FileOutputStream(tempFile);
-            BinaryDictInputOutput.writeDictionaryBinary(out, mFusionDictionary, FORMAT_OPTIONS);
-            out.flush();
-            out.close();
-            tempFile.renameTo(file);
-            clearFusionDictionary();
-        } catch (IOException e) {
-            Log.e(TAG, "IO exception while writing file", e);
-        } catch (UnsupportedFormatException e) {
-            Log.e(TAG, "Unsupported format", e);
-        } finally {
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-            }
+        if (needsToReloadBeforeWriting()) {
+            mDictionaryWriter.clear();
+            loadDictionaryAsync();
         }
+        mDictionaryWriter.write(mFilename);
     }
 
     /**
@@ -389,7 +367,7 @@
     private final void syncReloadDictionaryInternal() {
         // Ensure that only one thread attempts to read or write to the shared binary dictionary
         // file at the same time.
-        mSharedDictionaryController.lock();
+        mSharedDictionaryController.writeLock().lock();
         try {
             final long time = SystemClock.uptimeMillis();
             final boolean dictionaryFileExists = dictionaryFileExists();
@@ -415,9 +393,15 @@
                 // shared dictionary.
                 loadBinaryDictionary();
             }
+            if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) {
+                // Binary dictionary is not valid. Regenerate the dictionary file.
+                mSharedDictionaryController.mLastUpdateTime = time;
+                generateBinaryDictionary();
+                loadBinaryDictionary();
+            }
             mLocalDictionaryController.mLastUpdateTime = time;
         } finally {
-            mSharedDictionaryController.unlock();
+            mSharedDictionaryController.writeLock().unlock();
         }
     }
 
@@ -442,7 +426,7 @@
      * dictionary is out of date. Can be shared across multiple dictionary instances that access the
      * same filename.
      */
-    private static class DictionaryController extends ReentrantLock {
+    private static class DictionaryController extends ReentrantReadWriteLock {
         private volatile long mLastUpdateTime = 0;
         private volatile long mLastUpdateRequestTime = 0;
 
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index a67c919..2666573 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -728,172 +728,206 @@
      * to their base characters.  If c is in range, BASE_CHARS[c] == c
      * if c is not a combined character, or the base character if it
      * is combined.
+     *
+     * cf. native/jni/src/utils/char_utils.cpp
      */
     private static final char BASE_CHARS[] = {
-        0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
-        0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
-        0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
-        0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
-        0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
-        0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
-        0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
-        0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
-        0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
-        0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
-        0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
-        0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
-        0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
-        0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
-        0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
-        0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
-        0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
-        0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
-        0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
-        0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
-        0x0020, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
-        0x0020, 0x00a9, 0x0061, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x0020,
-        0x00b0, 0x00b1, 0x0032, 0x0033, 0x0020, 0x03bc, 0x00b6, 0x00b7,
-        0x0020, 0x0031, 0x006f, 0x00bb, 0x0031, 0x0031, 0x0033, 0x00bf,
-        0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00c6, 0x0043,
-        0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
-        0x00d0, 0x004e, 0x004f, 0x004f, 0x004f, 0x004f, 0x004f, 0x00d7,
-        0x004f, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00de, 0x0073, // Manually changed d8 to 4f
-                                                                        // Manually changed df to 73
-        0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00e6, 0x0063,
-        0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
-        0x00f0, 0x006e, 0x006f, 0x006f, 0x006f, 0x006f, 0x006f, 0x00f7,
-        0x006f, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00fe, 0x0079, // Manually changed f8 to 6f
-        0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
-        0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
-        0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
-        0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
-        0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
-        0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
-        0x0049, 0x0131, 0x0049, 0x0069, 0x004a, 0x006a, 0x004b, 0x006b,
-        0x0138, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c, 0x006c, 0x004c,
-        0x006c, 0x0141, 0x0142, 0x004e, 0x006e, 0x004e, 0x006e, 0x004e,
-        0x006e, 0x02bc, 0x014a, 0x014b, 0x004f, 0x006f, 0x004f, 0x006f,
-        0x004f, 0x006f, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
-        0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
-        0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
-        0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
-        0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
-        0x0059, 0x005a, 0x007a, 0x005a, 0x007a, 0x005a, 0x007a, 0x0073,
-        0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
-        0x0188, 0x0189, 0x018a, 0x018b, 0x018c, 0x018d, 0x018e, 0x018f,
-        0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
-        0x0198, 0x0199, 0x019a, 0x019b, 0x019c, 0x019d, 0x019e, 0x019f,
-        0x004f, 0x006f, 0x01a2, 0x01a3, 0x01a4, 0x01a5, 0x01a6, 0x01a7,
-        0x01a8, 0x01a9, 0x01aa, 0x01ab, 0x01ac, 0x01ad, 0x01ae, 0x0055,
-        0x0075, 0x01b1, 0x01b2, 0x01b3, 0x01b4, 0x01b5, 0x01b6, 0x01b7,
-        0x01b8, 0x01b9, 0x01ba, 0x01bb, 0x01bc, 0x01bd, 0x01be, 0x01bf,
-        0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x0044, 0x0044, 0x0064, 0x004c,
-        0x004c, 0x006c, 0x004e, 0x004e, 0x006e, 0x0041, 0x0061, 0x0049,
-        0x0069, 0x004f, 0x006f, 0x0055, 0x0075, 0x00dc, 0x00fc, 0x00dc,
-        0x00fc, 0x00dc, 0x00fc, 0x00dc, 0x00fc, 0x01dd, 0x00c4, 0x00e4,
-        0x0226, 0x0227, 0x00c6, 0x00e6, 0x01e4, 0x01e5, 0x0047, 0x0067,
-        0x004b, 0x006b, 0x004f, 0x006f, 0x01ea, 0x01eb, 0x01b7, 0x0292,
-        0x006a, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01f6, 0x01f7,
-        0x004e, 0x006e, 0x00c5, 0x00e5, 0x00c6, 0x00e6, 0x00d8, 0x00f8,
-        0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
-        0x0049, 0x0069, 0x0049, 0x0069, 0x004f, 0x006f, 0x004f, 0x006f,
-        0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
-        0x0053, 0x0073, 0x0054, 0x0074, 0x021c, 0x021d, 0x0048, 0x0068,
-        0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
-        0x0045, 0x0065, 0x00d6, 0x00f6, 0x00d5, 0x00f5, 0x004f, 0x006f,
-        0x022e, 0x022f, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
-        0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f,
-        0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
-        0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f,
-        0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
-        0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
-        0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
-        0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
-        0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
-        0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
-        0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
-        0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
-        0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
-        0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
-        0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
-        0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
-        0x0068, 0x0266, 0x006a, 0x0072, 0x0279, 0x027b, 0x0281, 0x0077,
-        0x0079, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
-        0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
-        0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
-        0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
-        0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02de, 0x02df,
-        0x0263, 0x006c, 0x0073, 0x0078, 0x0295, 0x02e5, 0x02e6, 0x02e7,
-        0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
-        0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
-        0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff,
-        0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
-        0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f,
-        0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
-        0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f,
-        0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
-        0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f,
-        0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
-        0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f,
-        0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
-        0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f,
-        0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
-        0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
-        0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
-        0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
-        0x0370, 0x0371, 0x0372, 0x0373, 0x02b9, 0x0375, 0x0376, 0x0377,
-        0x0378, 0x0379, 0x0020, 0x037b, 0x037c, 0x037d, 0x003b, 0x037f,
-        0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00a8, 0x0391, 0x00b7,
-        0x0395, 0x0397, 0x0399, 0x038b, 0x039f, 0x038d, 0x03a5, 0x03a9,
-        0x03ca, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
-        0x0398, 0x0399, 0x039a, 0x039b, 0x039c, 0x039d, 0x039e, 0x039f,
-        0x03a0, 0x03a1, 0x03a2, 0x03a3, 0x03a4, 0x03a5, 0x03a6, 0x03a7,
-        0x03a8, 0x03a9, 0x0399, 0x03a5, 0x03b1, 0x03b5, 0x03b7, 0x03b9,
-        0x03cb, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
-        0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
-        0x03c0, 0x03c1, 0x03c2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
-        0x03c8, 0x03c9, 0x03b9, 0x03c5, 0x03bf, 0x03c5, 0x03c9, 0x03cf,
-        0x03b2, 0x03b8, 0x03a5, 0x03d2, 0x03d2, 0x03c6, 0x03c0, 0x03d7,
-        0x03d8, 0x03d9, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df,
-        0x03e0, 0x03e1, 0x03e2, 0x03e3, 0x03e4, 0x03e5, 0x03e6, 0x03e7,
-        0x03e8, 0x03e9, 0x03ea, 0x03eb, 0x03ec, 0x03ed, 0x03ee, 0x03ef,
-        0x03ba, 0x03c1, 0x03c2, 0x03f3, 0x0398, 0x03b5, 0x03f6, 0x03f7,
-        0x03f8, 0x03a3, 0x03fa, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff,
-        0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
-        0x0408, 0x0409, 0x040a, 0x040b, 0x041a, 0x0418, 0x0423, 0x040f,
-        0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
-        0x0418, 0x0418, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f,
-        0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
-        0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f,
-        0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
-        0x0438, 0x0438, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
-        0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
-        0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
-        0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
-        0x0458, 0x0459, 0x045a, 0x045b, 0x043a, 0x0438, 0x0443, 0x045f,
-        0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
-        0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, 0x046f,
-        0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
-        0x0478, 0x0479, 0x047a, 0x047b, 0x047c, 0x047d, 0x047e, 0x047f,
-        0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
-        0x0488, 0x0489, 0x048a, 0x048b, 0x048c, 0x048d, 0x048e, 0x048f,
-        0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
-        0x0498, 0x0499, 0x049a, 0x049b, 0x049c, 0x049d, 0x049e, 0x049f,
-        0x04a0, 0x04a1, 0x04a2, 0x04a3, 0x04a4, 0x04a5, 0x04a6, 0x04a7,
-        0x04a8, 0x04a9, 0x04aa, 0x04ab, 0x04ac, 0x04ad, 0x04ae, 0x04af,
-        0x04b0, 0x04b1, 0x04b2, 0x04b3, 0x04b4, 0x04b5, 0x04b6, 0x04b7,
-        0x04b8, 0x04b9, 0x04ba, 0x04bb, 0x04bc, 0x04bd, 0x04be, 0x04bf,
-        0x04c0, 0x0416, 0x0436, 0x04c3, 0x04c4, 0x04c5, 0x04c6, 0x04c7,
-        0x04c8, 0x04c9, 0x04ca, 0x04cb, 0x04cc, 0x04cd, 0x04ce, 0x04cf,
-        0x0410, 0x0430, 0x0410, 0x0430, 0x04d4, 0x04d5, 0x0415, 0x0435,
-        0x04d8, 0x04d9, 0x04d8, 0x04d9, 0x0416, 0x0436, 0x0417, 0x0437,
-        0x04e0, 0x04e1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041e, 0x043e,
-        0x04e8, 0x04e9, 0x04e8, 0x04e9, 0x042d, 0x044d, 0x0423, 0x0443,
-        0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04f6, 0x04f7,
-        0x042b, 0x044b, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff,
+        /* U+0000 */ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+        /* U+0008 */ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F,
+        /* U+0010 */ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+        /* U+0018 */ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F,
+        /* U+0020 */ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+        /* U+0028 */ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
+        /* U+0030 */ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+        /* U+0038 */ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
+        /* U+0040 */ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+        /* U+0048 */ 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
+        /* U+0050 */ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+        /* U+0058 */ 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
+        /* U+0060 */ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+        /* U+0068 */ 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
+        /* U+0070 */ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+        /* U+0078 */ 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F,
+        /* U+0080 */ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+        /* U+0088 */ 0x0088, 0x0089, 0x008A, 0x008B, 0x008C, 0x008D, 0x008E, 0x008F,
+        /* U+0090 */ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+        /* U+0098 */ 0x0098, 0x0099, 0x009A, 0x009B, 0x009C, 0x009D, 0x009E, 0x009F,
+        /* U+00A0 */ 0x0020, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7,
+        /* U+00A8 */ 0x0020, 0x00A9, 0x0061, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0020,
+        /* U+00B0 */ 0x00B0, 0x00B1, 0x0032, 0x0033, 0x0020, 0x03BC, 0x00B6, 0x00B7,
+        /* U+00B8 */ 0x0020, 0x0031, 0x006F, 0x00BB, 0x0031, 0x0031, 0x0033, 0x00BF,
+        /* U+00C0 */ 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x0041, 0x00C6, 0x0043,
+        /* U+00C8 */ 0x0045, 0x0045, 0x0045, 0x0045, 0x0049, 0x0049, 0x0049, 0x0049,
+        /* U+00D0 */ 0x00D0, 0x004E, 0x004F, 0x004F, 0x004F, 0x004F, 0x004F, 0x00D7,
+        /* U+00D8 */ 0x004F, 0x0055, 0x0055, 0x0055, 0x0055, 0x0059, 0x00DE, 0x0073,
+            // U+00D8: Manually changed from 00D8 to 004F
+              // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+            // U+00DF: Manually changed from 00DF to 0073
+        /* U+00E0 */ 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x0061, 0x00E6, 0x0063,
+        /* U+00E8 */ 0x0065, 0x0065, 0x0065, 0x0065, 0x0069, 0x0069, 0x0069, 0x0069,
+        /* U+00F0 */ 0x00F0, 0x006E, 0x006F, 0x006F, 0x006F, 0x006F, 0x006F, 0x00F7,
+        /* U+00F8 */ 0x006F, 0x0075, 0x0075, 0x0075, 0x0075, 0x0079, 0x00FE, 0x0079,
+            // U+00F8: Manually changed from 00F8 to 006F
+              // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+        /* U+0100 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0041, 0x0061, 0x0043, 0x0063,
+        /* U+0108 */ 0x0043, 0x0063, 0x0043, 0x0063, 0x0043, 0x0063, 0x0044, 0x0064,
+        /* U+0110 */ 0x0110, 0x0111, 0x0045, 0x0065, 0x0045, 0x0065, 0x0045, 0x0065,
+        /* U+0118 */ 0x0045, 0x0065, 0x0045, 0x0065, 0x0047, 0x0067, 0x0047, 0x0067,
+        /* U+0120 */ 0x0047, 0x0067, 0x0047, 0x0067, 0x0048, 0x0068, 0x0126, 0x0127,
+        /* U+0128 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069, 0x0049, 0x0069,
+        /* U+0130 */ 0x0049, 0x0131, 0x0049, 0x0069, 0x004A, 0x006A, 0x004B, 0x006B,
+        /* U+0138 */ 0x0138, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C, 0x006C, 0x004C,
+        /* U+0140 */ 0x006C, 0x004C, 0x006C, 0x004E, 0x006E, 0x004E, 0x006E, 0x004E,
+            // U+0141: Manually changed from 0141 to 004C
+            // U+0142: Manually changed from 0142 to 006C
+        /* U+0148 */ 0x006E, 0x02BC, 0x014A, 0x014B, 0x004F, 0x006F, 0x004F, 0x006F,
+        /* U+0150 */ 0x004F, 0x006F, 0x0152, 0x0153, 0x0052, 0x0072, 0x0052, 0x0072,
+        /* U+0158 */ 0x0052, 0x0072, 0x0053, 0x0073, 0x0053, 0x0073, 0x0053, 0x0073,
+        /* U+0160 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x0054, 0x0074, 0x0166, 0x0167,
+        /* U+0168 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055, 0x0075,
+        /* U+0170 */ 0x0055, 0x0075, 0x0055, 0x0075, 0x0057, 0x0077, 0x0059, 0x0079,
+        /* U+0178 */ 0x0059, 0x005A, 0x007A, 0x005A, 0x007A, 0x005A, 0x007A, 0x0073,
+        /* U+0180 */ 0x0180, 0x0181, 0x0182, 0x0183, 0x0184, 0x0185, 0x0186, 0x0187,
+        /* U+0188 */ 0x0188, 0x0189, 0x018A, 0x018B, 0x018C, 0x018D, 0x018E, 0x018F,
+        /* U+0190 */ 0x0190, 0x0191, 0x0192, 0x0193, 0x0194, 0x0195, 0x0196, 0x0197,
+        /* U+0198 */ 0x0198, 0x0199, 0x019A, 0x019B, 0x019C, 0x019D, 0x019E, 0x019F,
+        /* U+01A0 */ 0x004F, 0x006F, 0x01A2, 0x01A3, 0x01A4, 0x01A5, 0x01A6, 0x01A7,
+        /* U+01A8 */ 0x01A8, 0x01A9, 0x01AA, 0x01AB, 0x01AC, 0x01AD, 0x01AE, 0x0055,
+        /* U+01B0 */ 0x0075, 0x01B1, 0x01B2, 0x01B3, 0x01B4, 0x01B5, 0x01B6, 0x01B7,
+        /* U+01B8 */ 0x01B8, 0x01B9, 0x01BA, 0x01BB, 0x01BC, 0x01BD, 0x01BE, 0x01BF,
+        /* U+01C0 */ 0x01C0, 0x01C1, 0x01C2, 0x01C3, 0x0044, 0x0044, 0x0064, 0x004C,
+        /* U+01C8 */ 0x004C, 0x006C, 0x004E, 0x004E, 0x006E, 0x0041, 0x0061, 0x0049,
+        /* U+01D0 */ 0x0069, 0x004F, 0x006F, 0x0055, 0x0075, 0x0055, 0x0075, 0x0055,
+            // U+01D5: Manually changed from 00DC to 0055
+            // U+01D6: Manually changed from 00FC to 0075
+            // U+01D7: Manually changed from 00DC to 0055
+        /* U+01D8 */ 0x0075, 0x0055, 0x0075, 0x0055, 0x0075, 0x01DD, 0x0041, 0x0061,
+            // U+01D8: Manually changed from 00FC to 0075
+            // U+01D9: Manually changed from 00DC to 0055
+            // U+01DA: Manually changed from 00FC to 0075
+            // U+01DB: Manually changed from 00DC to 0055
+            // U+01DC: Manually changed from 00FC to 0075
+            // U+01DE: Manually changed from 00C4 to 0041
+            // U+01DF: Manually changed from 00E4 to 0061
+        /* U+01E0 */ 0x0041, 0x0061, 0x00C6, 0x00E6, 0x01E4, 0x01E5, 0x0047, 0x0067,
+            // U+01E0: Manually changed from 0226 to 0041
+            // U+01E1: Manually changed from 0227 to 0061
+        /* U+01E8 */ 0x004B, 0x006B, 0x004F, 0x006F, 0x004F, 0x006F, 0x01B7, 0x0292,
+            // U+01EC: Manually changed from 01EA to 004F
+            // U+01ED: Manually changed from 01EB to 006F
+        /* U+01F0 */ 0x006A, 0x0044, 0x0044, 0x0064, 0x0047, 0x0067, 0x01F6, 0x01F7,
+        /* U+01F8 */ 0x004E, 0x006E, 0x0041, 0x0061, 0x00C6, 0x00E6, 0x004F, 0x006F,
+            // U+01FA: Manually changed from 00C5 to 0041
+            // U+01FB: Manually changed from 00E5 to 0061
+            // U+01FE: Manually changed from 00D8 to 004F
+              // TODO: Check if it's really acceptable to consider Ø a diacritical variant of O
+            // U+01FF: Manually changed from 00F8 to 006F
+              // TODO: Check if it's really acceptable to consider ø a diacritical variant of o
+        /* U+0200 */ 0x0041, 0x0061, 0x0041, 0x0061, 0x0045, 0x0065, 0x0045, 0x0065,
+        /* U+0208 */ 0x0049, 0x0069, 0x0049, 0x0069, 0x004F, 0x006F, 0x004F, 0x006F,
+        /* U+0210 */ 0x0052, 0x0072, 0x0052, 0x0072, 0x0055, 0x0075, 0x0055, 0x0075,
+        /* U+0218 */ 0x0053, 0x0073, 0x0054, 0x0074, 0x021C, 0x021D, 0x0048, 0x0068,
+        /* U+0220 */ 0x0220, 0x0221, 0x0222, 0x0223, 0x0224, 0x0225, 0x0041, 0x0061,
+        /* U+0228 */ 0x0045, 0x0065, 0x004F, 0x006F, 0x004F, 0x006F, 0x004F, 0x006F,
+            // U+022A: Manually changed from 00D6 to 004F
+            // U+022B: Manually changed from 00F6 to 006F
+            // U+022C: Manually changed from 00D5 to 004F
+            // U+022D: Manually changed from 00F5 to 006F
+        /* U+0230 */ 0x004F, 0x006F, 0x0059, 0x0079, 0x0234, 0x0235, 0x0236, 0x0237,
+            // U+0230: Manually changed from 022E to 004F
+            // U+0231: Manually changed from 022F to 006F
+        /* U+0238 */ 0x0238, 0x0239, 0x023A, 0x023B, 0x023C, 0x023D, 0x023E, 0x023F,
+        /* U+0240 */ 0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
+        /* U+0248 */ 0x0248, 0x0249, 0x024A, 0x024B, 0x024C, 0x024D, 0x024E, 0x024F,
+        /* U+0250 */ 0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+        /* U+0258 */ 0x0258, 0x0259, 0x025A, 0x025B, 0x025C, 0x025D, 0x025E, 0x025F,
+        /* U+0260 */ 0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+        /* U+0268 */ 0x0268, 0x0269, 0x026A, 0x026B, 0x026C, 0x026D, 0x026E, 0x026F,
+        /* U+0270 */ 0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+        /* U+0278 */ 0x0278, 0x0279, 0x027A, 0x027B, 0x027C, 0x027D, 0x027E, 0x027F,
+        /* U+0280 */ 0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+        /* U+0288 */ 0x0288, 0x0289, 0x028A, 0x028B, 0x028C, 0x028D, 0x028E, 0x028F,
+        /* U+0290 */ 0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+        /* U+0298 */ 0x0298, 0x0299, 0x029A, 0x029B, 0x029C, 0x029D, 0x029E, 0x029F,
+        /* U+02A0 */ 0x02A0, 0x02A1, 0x02A2, 0x02A3, 0x02A4, 0x02A5, 0x02A6, 0x02A7,
+        /* U+02A8 */ 0x02A8, 0x02A9, 0x02AA, 0x02AB, 0x02AC, 0x02AD, 0x02AE, 0x02AF,
+        /* U+02B0 */ 0x0068, 0x0266, 0x006A, 0x0072, 0x0279, 0x027B, 0x0281, 0x0077,
+        /* U+02B8 */ 0x0079, 0x02B9, 0x02BA, 0x02BB, 0x02BC, 0x02BD, 0x02BE, 0x02BF,
+        /* U+02C0 */ 0x02C0, 0x02C1, 0x02C2, 0x02C3, 0x02C4, 0x02C5, 0x02C6, 0x02C7,
+        /* U+02C8 */ 0x02C8, 0x02C9, 0x02CA, 0x02CB, 0x02CC, 0x02CD, 0x02CE, 0x02CF,
+        /* U+02D0 */ 0x02D0, 0x02D1, 0x02D2, 0x02D3, 0x02D4, 0x02D5, 0x02D6, 0x02D7,
+        /* U+02D8 */ 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x02DE, 0x02DF,
+        /* U+02E0 */ 0x0263, 0x006C, 0x0073, 0x0078, 0x0295, 0x02E5, 0x02E6, 0x02E7,
+        /* U+02E8 */ 0x02E8, 0x02E9, 0x02EA, 0x02EB, 0x02EC, 0x02ED, 0x02EE, 0x02EF,
+        /* U+02F0 */ 0x02F0, 0x02F1, 0x02F2, 0x02F3, 0x02F4, 0x02F5, 0x02F6, 0x02F7,
+        /* U+02F8 */ 0x02F8, 0x02F9, 0x02FA, 0x02FB, 0x02FC, 0x02FD, 0x02FE, 0x02FF,
+        /* U+0300 */ 0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+        /* U+0308 */ 0x0308, 0x0309, 0x030A, 0x030B, 0x030C, 0x030D, 0x030E, 0x030F,
+        /* U+0310 */ 0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+        /* U+0318 */ 0x0318, 0x0319, 0x031A, 0x031B, 0x031C, 0x031D, 0x031E, 0x031F,
+        /* U+0320 */ 0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+        /* U+0328 */ 0x0328, 0x0329, 0x032A, 0x032B, 0x032C, 0x032D, 0x032E, 0x032F,
+        /* U+0330 */ 0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+        /* U+0338 */ 0x0338, 0x0339, 0x033A, 0x033B, 0x033C, 0x033D, 0x033E, 0x033F,
+        /* U+0340 */ 0x0300, 0x0301, 0x0342, 0x0313, 0x0308, 0x0345, 0x0346, 0x0347,
+        /* U+0348 */ 0x0348, 0x0349, 0x034A, 0x034B, 0x034C, 0x034D, 0x034E, 0x034F,
+        /* U+0350 */ 0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+        /* U+0358 */ 0x0358, 0x0359, 0x035A, 0x035B, 0x035C, 0x035D, 0x035E, 0x035F,
+        /* U+0360 */ 0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+        /* U+0368 */ 0x0368, 0x0369, 0x036A, 0x036B, 0x036C, 0x036D, 0x036E, 0x036F,
+        /* U+0370 */ 0x0370, 0x0371, 0x0372, 0x0373, 0x02B9, 0x0375, 0x0376, 0x0377,
+        /* U+0378 */ 0x0378, 0x0379, 0x0020, 0x037B, 0x037C, 0x037D, 0x003B, 0x037F,
+        /* U+0380 */ 0x0380, 0x0381, 0x0382, 0x0383, 0x0020, 0x00A8, 0x0391, 0x00B7,
+        /* U+0388 */ 0x0395, 0x0397, 0x0399, 0x038B, 0x039F, 0x038D, 0x03A5, 0x03A9,
+        /* U+0390 */ 0x03CA, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397,
+        /* U+0398 */ 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F,
+        /* U+03A0 */ 0x03A0, 0x03A1, 0x03A2, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7,
+        /* U+03A8 */ 0x03A8, 0x03A9, 0x0399, 0x03A5, 0x03B1, 0x03B5, 0x03B7, 0x03B9,
+        /* U+03B0 */ 0x03CB, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7,
+        /* U+03B8 */ 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF,
+        /* U+03C0 */ 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7,
+        /* U+03C8 */ 0x03C8, 0x03C9, 0x03B9, 0x03C5, 0x03BF, 0x03C5, 0x03C9, 0x03CF,
+        /* U+03D0 */ 0x03B2, 0x03B8, 0x03A5, 0x03D2, 0x03D2, 0x03C6, 0x03C0, 0x03D7,
+        /* U+03D8 */ 0x03D8, 0x03D9, 0x03DA, 0x03DB, 0x03DC, 0x03DD, 0x03DE, 0x03DF,
+        /* U+03E0 */ 0x03E0, 0x03E1, 0x03E2, 0x03E3, 0x03E4, 0x03E5, 0x03E6, 0x03E7,
+        /* U+03E8 */ 0x03E8, 0x03E9, 0x03EA, 0x03EB, 0x03EC, 0x03ED, 0x03EE, 0x03EF,
+        /* U+03F0 */ 0x03BA, 0x03C1, 0x03C2, 0x03F3, 0x0398, 0x03B5, 0x03F6, 0x03F7,
+        /* U+03F8 */ 0x03F8, 0x03A3, 0x03FA, 0x03FB, 0x03FC, 0x03FD, 0x03FE, 0x03FF,
+        /* U+0400 */ 0x0415, 0x0415, 0x0402, 0x0413, 0x0404, 0x0405, 0x0406, 0x0406,
+        /* U+0408 */ 0x0408, 0x0409, 0x040A, 0x040B, 0x041A, 0x0418, 0x0423, 0x040F,
+        /* U+0410 */ 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417,
+        /* U+0418 */ 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F,
+            // U+0419: Manually changed from 0418 to 0419
+        /* U+0420 */ 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427,
+        /* U+0428 */ 0x0428, 0x0429, 0x042C, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+            // U+042A: Manually changed from 042A to 042C
+        /* U+0430 */ 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+        /* U+0438 */ 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F,
+            // U+0439: Manually changed from 0438 to 0439
+        /* U+0440 */ 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+        /* U+0448 */ 0x0448, 0x0449, 0x044C, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+            // U+044A: Manually changed from 044A to 044C
+        /* U+0450 */ 0x0435, 0x0435, 0x0452, 0x0433, 0x0454, 0x0455, 0x0456, 0x0456,
+        /* U+0458 */ 0x0458, 0x0459, 0x045A, 0x045B, 0x043A, 0x0438, 0x0443, 0x045F,
+        /* U+0460 */ 0x0460, 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467,
+        /* U+0468 */ 0x0468, 0x0469, 0x046A, 0x046B, 0x046C, 0x046D, 0x046E, 0x046F,
+        /* U+0470 */ 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0474, 0x0475,
+        /* U+0478 */ 0x0478, 0x0479, 0x047A, 0x047B, 0x047C, 0x047D, 0x047E, 0x047F,
+        /* U+0480 */ 0x0480, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+        /* U+0488 */ 0x0488, 0x0489, 0x048A, 0x048B, 0x048C, 0x048D, 0x048E, 0x048F,
+        /* U+0490 */ 0x0490, 0x0491, 0x0492, 0x0493, 0x0494, 0x0495, 0x0496, 0x0497,
+        /* U+0498 */ 0x0498, 0x0499, 0x049A, 0x049B, 0x049C, 0x049D, 0x049E, 0x049F,
+        /* U+04A0 */ 0x04A0, 0x04A1, 0x04A2, 0x04A3, 0x04A4, 0x04A5, 0x04A6, 0x04A7,
+        /* U+04A8 */ 0x04A8, 0x04A9, 0x04AA, 0x04AB, 0x04AC, 0x04AD, 0x04AE, 0x04AF,
+        /* U+04B0 */ 0x04B0, 0x04B1, 0x04B2, 0x04B3, 0x04B4, 0x04B5, 0x04B6, 0x04B7,
+        /* U+04B8 */ 0x04B8, 0x04B9, 0x04BA, 0x04BB, 0x04BC, 0x04BD, 0x04BE, 0x04BF,
+        /* U+04C0 */ 0x04C0, 0x0416, 0x0436, 0x04C3, 0x04C4, 0x04C5, 0x04C6, 0x04C7,
+        /* U+04C8 */ 0x04C8, 0x04C9, 0x04CA, 0x04CB, 0x04CC, 0x04CD, 0x04CE, 0x04CF,
+        /* U+04D0 */ 0x0410, 0x0430, 0x0410, 0x0430, 0x04D4, 0x04D5, 0x0415, 0x0435,
+        /* U+04D8 */ 0x04D8, 0x04D9, 0x04D8, 0x04D9, 0x0416, 0x0436, 0x0417, 0x0437,
+        /* U+04E0 */ 0x04E0, 0x04E1, 0x0418, 0x0438, 0x0418, 0x0438, 0x041E, 0x043E,
+        /* U+04E8 */ 0x04E8, 0x04E9, 0x04E8, 0x04E9, 0x042D, 0x044D, 0x0423, 0x0443,
+        /* U+04F0 */ 0x0423, 0x0443, 0x0423, 0x0443, 0x0427, 0x0447, 0x04F6, 0x04F7,
+        /* U+04F8 */ 0x042B, 0x044B, 0x04FA, 0x04FB, 0x04FC, 0x04FD, 0x04FE, 0x04FF,
     };
-
-    // generated with:
-    // cat UnicodeData.txt | perl -e 'while (<>) { @foo = split(/;/); $foo[5] =~ s/<.*> //; $base[hex($foo[0])] = hex($foo[5]);} for ($i = 0; $i < 0x500; $i += 8) { for ($j = $i; $j < $i + 8; $j++) { printf("0x%04x, ", $base[$j] ? $base[$j] : $j)}; print "\n"; }'
-
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 0560cf5..8f5e571 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -73,22 +73,22 @@
 import com.android.inputmethod.keyboard.KeyboardId;
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.MainKeyboardView;
-import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.CollectionUtils;
 import com.android.inputmethod.latin.utils.CompletionInfoUtils;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
 import com.android.inputmethod.latin.utils.IntentUtils;
 import com.android.inputmethod.latin.utils.JniUtils;
+import com.android.inputmethod.latin.utils.LatinImeLoggerUtils;
 import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition;
 import com.android.inputmethod.latin.utils.RecapitalizeStatus;
 import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper;
 import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask;
-import com.android.inputmethod.latin.utils.Utils;
-import com.android.inputmethod.latin.utils.Utils.Stats;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.ResearchLogger;
 
 import java.io.FileDescriptor;
@@ -961,7 +961,11 @@
             // TODO: is it still necessary to test for composingSpan related stuff?
             final boolean selectionChangedOrSafeToReset = selectionChanged
                     || (!mWordComposer.isComposingWord()) || noComposingSpan;
-            if (selectionChangedOrSafeToReset) {
+            final boolean hasOrHadSelection = (oldSelStart != oldSelEnd
+                    || newSelStart != newSelEnd);
+            final int moveAmount = newSelStart - oldSelStart;
+            if (selectionChangedOrSafeToReset && (hasOrHadSelection
+                    || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) {
                 // If we are composing a word and moving the cursor, we would want to set a
                 // suggestion span for recorrection to work correctly. Unfortunately, that
                 // would involve the keyboard committing some new text, which would move the
@@ -1356,14 +1360,11 @@
         showSubtypeSelectorAndSettings();
     }
 
-    // Virtual codes representing custom requests.  These are used in onCustomRequest() below.
-    public static final int CODE_SHOW_INPUT_METHOD_PICKER = 1;
-
     @Override
     public boolean onCustomRequest(final int requestCode) {
         if (isShowingOptionDialog()) return false;
         switch (requestCode) {
-        case CODE_SHOW_INPUT_METHOD_PICKER:
+        case Constants.CUSTOM_CODE_SHOW_INPUT_METHOD_PICKER:
             if (mRichImm.hasMultipleEnabledIMEsOrSubtypes(true /* include aux subtypes */)) {
                 mRichImm.getInputMethodManager().showInputMethodPicker();
                 return true;
@@ -1550,7 +1551,7 @@
             if (SPACE_STATE_PHANTOM == spaceState) {
                 if (mSettings.isInternal()) {
                     if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) {
-                        Stats.onAutoCorrection(
+                        LatinImeLoggerUtils.onAutoCorrection(
                                 "", mWordComposer.getTypedWord(), " ", mWordComposer);
                     }
                 }
@@ -1611,7 +1612,8 @@
         if (mWordComposer.isComposingWord()) {
             if (mSettings.isInternal()) {
                 if (mWordComposer.isBatchMode()) {
-                    Stats.onAutoCorrection("", mWordComposer.getTypedWord(), " ", mWordComposer);
+                    LatinImeLoggerUtils.onAutoCorrection(
+                            "", mWordComposer.getTypedWord(), " ", mWordComposer);
                 }
             }
             final int wordComposerSize = mWordComposer.size();
@@ -1859,7 +1861,7 @@
         } else {
             if (mLastComposedWord.canRevertCommit()) {
                 if (mSettings.isInternal()) {
-                    Stats.onAutoCorrectionCancellation();
+                    LatinImeLoggerUtils.onAutoCorrectionCancellation();
                 }
                 revertCommit();
                 return;
@@ -2030,7 +2032,7 @@
         }
         mHandler.postUpdateSuggestionStrip();
         if (mSettings.isInternal()) {
-            Utils.Stats.onNonSeparator((char)primaryCode, x, y);
+            LatinImeLoggerUtils.onNonSeparator((char)primaryCode, x, y);
         }
     }
 
@@ -2135,7 +2137,7 @@
             setPunctuationSuggestions();
         }
         if (mSettings.isInternal()) {
-            Utils.Stats.onSeparator((char)primaryCode, x, y);
+            LatinImeLoggerUtils.onSeparator((char)primaryCode, x, y);
         }
 
         mKeyboardSwitcher.updateShiftState();
@@ -2324,7 +2326,8 @@
                         + "is empty? Impossible! I must commit suicide.");
             }
             if (mSettings.isInternal()) {
-                Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
+                LatinImeLoggerUtils.onAutoCorrection(
+                        typedWord, autoCorrection, separatorString, mWordComposer);
             }
             if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
                 final SuggestedWords suggestedWords = mSuggestedWords;
@@ -2428,7 +2431,7 @@
                         && !AutoCorrection.isValidWord(mSuggest, suggestion, true);
 
         if (mSettings.isInternal()) {
-            Stats.onSeparator((char)Constants.CODE_SPACE,
+            LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (showingAddToDictionaryHint && mIsUserDictionaryAvailable) {
@@ -2514,7 +2517,7 @@
         // If we don't know the cursor location, return.
         if (mLastSelectionStart < 0) return;
         if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return;
-        final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
+        final TextRange range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(),
                 0 /* additionalPrecedingWordsCount */);
         if (null == range) return; // Happens if we don't have an input connection at all
         // If for some strange reason (editor bug or so) we measure the text before the cursor as
@@ -2535,6 +2538,7 @@
             }
         }
         mWordComposer.setComposingWord(typedWord, mKeyboardSwitcher.getKeyboard());
+        // TODO: this is in chars but the callee expects code points!
         mWordComposer.setCursorPositionWithinWord(numberOfCharsInWordBeforeCursor);
         mConnection.setComposingRegion(
                 mLastSelectionStart - numberOfCharsInWordBeforeCursor,
@@ -2631,7 +2635,7 @@
         }
         mConnection.commitText(originallyTypedWord + mLastComposedWord.mSeparatorString, 1);
         if (mSettings.isInternal()) {
-            Stats.onSeparator(mLastComposedWord.mSeparatorString,
+            LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString,
                     Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE);
         }
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
@@ -2673,15 +2677,22 @@
         }
     }
 
-    // Callback called by PointerTracker through the KeyboardActionListener. This is called when a
-    // key is depressed; release matching call is onReleaseKey below.
+    // Callback of the {@link KeyboardActionListener}. This is called when a key is depressed;
+    // release matching call is {@link #onReleaseKey(int,boolean)} below.
     @Override
     public void onPressKey(final int primaryCode, final boolean isSinglePointer) {
         mKeyboardSwitcher.onPressKey(primaryCode, isSinglePointer);
+        final MainKeyboardView mKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
+        final boolean noFeedback = (mKeyboardView != null && mKeyboardView.isInSlidingKeyInput())
+                || (primaryCode == Constants.CODE_DELETE && !mConnection.canDeleteCharacters());
+        if (!noFeedback) {
+            AudioAndHapticFeedbackManager.getInstance().hapticAndAudioFeedback(
+                    primaryCode, mKeyboardView);
+        }
     }
 
-    // Callback by PointerTracker through the KeyboardActionListener. This is called when a key
-    // is released; press matching call is onPressKey above.
+    // Callback of the {@link KeyboardActionListener}. This is called when a key is released;
+    // press matching call is {@link #onPressKey(int,boolean)} above.
     @Override
     public void onReleaseKey(final int primaryCode, final boolean withSliding) {
         mKeyboardSwitcher.onReleaseKey(primaryCode, withSliding);
@@ -2778,7 +2789,7 @@
         final CharSequence[] items = new CharSequence[] {
                 // TODO: Should use new string "Select active input modes".
                 getString(R.string.language_selection_title),
-                getString(Utils.getAcitivityTitleResId(this, SettingsActivity.class)),
+                getString(ApplicationUtils.getAcitivityTitleResId(this, SettingsActivity.class)),
         };
         final DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
             @Override
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 6b22cb1..461de53 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -17,9 +17,7 @@
 package com.android.inputmethod.latin;
 
 import android.inputmethodservice.InputMethodService;
-import android.text.Spanned;
 import android.text.TextUtils;
-import android.text.style.SuggestionSpan;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -32,9 +30,9 @@
 import com.android.inputmethod.latin.utils.CapsModeUtils;
 import com.android.inputmethod.latin.utils.DebugLogUtils;
 import com.android.inputmethod.latin.utils.StringUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.ResearchLogger;
 
-import java.util.Arrays;
 import java.util.Locale;
 import java.util.regex.Pattern;
 
@@ -193,6 +191,10 @@
         return mIC.getSelectedText(flags);
     }
 
+    public boolean canDeleteCharacters() {
+        return mCurrentCursorPosition > 0;
+    }
+
     /**
      * Gets the caps modes we should be in after this specific string.
      *
@@ -441,100 +443,6 @@
         return getNthPreviousWord(prev, sentenceSeperators, n);
     }
 
-    /**
-     * Represents a range of text, relative to the current cursor position.
-     */
-    public static final class Range {
-        private final CharSequence mTextAtCursor;
-        private final int mWordAtCursorStartIndex;
-        private final int mWordAtCursorEndIndex;
-        private final int mCursorIndex;
-
-        public final CharSequence mWord;
-
-        public int getNumberOfCharsInWordBeforeCursor() {
-            return mCursorIndex - mWordAtCursorStartIndex;
-        }
-
-        public int getNumberOfCharsInWordAfterCursor() {
-            return mWordAtCursorEndIndex - mCursorIndex;
-        }
-
-        /**
-         * Gets the suggestion spans that are put squarely on the word, with the exact start
-         * and end of the span matching the boundaries of the word.
-         * @return the list of spans.
-         */
-        public SuggestionSpan[] getSuggestionSpansAtWord() {
-            if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
-                return new SuggestionSpan[0];
-            }
-            final Spanned text = (Spanned)mTextAtCursor;
-            // Note: it's fine to pass indices negative or greater than the length of the string
-            // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
-            // spans were cut at the cursor position, and #getSpans(start, end) does not return
-            // spans that end at `start' or begin at `end'. Consider the following case:
-            //              this| is          (The | symbolizes the cursor position
-            //              ---- ---
-            // In this case, the cursor is in position 4, so the 0~7 span has been split into
-            // a 0~4 part and a 4~7 part.
-            // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
-            // of the span, and not the part from 4 to 7, so we would not realize the span actually
-            // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
-            // the 4~7 spans and we can merge them accordingly.
-            // Any span starting more than 1 char away from the word boundaries in any direction
-            // does not touch the word, so we don't need to consider it. That's why requesting
-            // -1 ~ +1 is enough.
-            // Of course this is only relevant if the cursor is at one end of the word. If it's
-            // in the middle, the -1 and +1 are not necessary, but they are harmless.
-            final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
-                    mWordAtCursorEndIndex + 1, SuggestionSpan.class);
-            int readIndex = 0;
-            int writeIndex = 0;
-            for (; readIndex < spans.length; ++readIndex) {
-                final SuggestionSpan span = spans[readIndex];
-                // The span may be null, as we null them when we find duplicates. Cf a few lines
-                // down.
-                if (null == span) continue;
-                // Tentative span start and end. This may be modified later if we realize the
-                // same span is also applied to other parts of the string.
-                int spanStart = text.getSpanStart(span);
-                int spanEnd = text.getSpanEnd(span);
-                for (int i = readIndex + 1; i < spans.length; ++i) {
-                    if (span.equals(spans[i])) {
-                        // We found the same span somewhere else. Read the new extent of this
-                        // span, and adjust our values accordingly.
-                        spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
-                        spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
-                        // ...and mark the span as processed.
-                        spans[i] = null;
-                    }
-                }
-                if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
-                    // If the span does not start and stop here, we ignore it. It probably extends
-                    // past the start or end of the word, as happens in missing space correction
-                    // or EasyEditSpans put by voice input.
-                    spans[writeIndex++] = spans[readIndex];
-                }
-            }
-            return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
-        }
-
-        public Range(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
-                final int wordAtCursorEndIndex, final int cursorIndex) {
-            if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
-                    || cursorIndex > wordAtCursorEndIndex
-                    || wordAtCursorEndIndex > textAtCursor.length()) {
-                throw new IndexOutOfBoundsException();
-            }
-            mTextAtCursor = textAtCursor;
-            mWordAtCursorStartIndex = wordAtCursorStartIndex;
-            mWordAtCursorEndIndex = wordAtCursorEndIndex;
-            mCursorIndex = cursorIndex;
-            mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
-        }
-    }
-
     private static boolean isSeparator(int code, String sep) {
         return sep.indexOf(code) != -1;
     }
@@ -581,7 +489,7 @@
      */
     public CharSequence getWordAtCursor(String separators) {
         // getWordRangeAtCursor returns null if the connection is null
-        Range r = getWordRangeAtCursor(separators, 0);
+        TextRange r = getWordRangeAtCursor(separators, 0);
         return (r == null) ? null : r.mWord;
     }
 
@@ -593,7 +501,8 @@
      *   be included in the returned range
      * @return a range containing the text surrounding the cursor
      */
-    public Range getWordRangeAtCursor(final String sep, final int additionalPrecedingWordsCount) {
+    public TextRange getWordRangeAtCursor(final String sep,
+            final int additionalPrecedingWordsCount) {
         mIC = mParent.getCurrentInputConnection();
         if (mIC == null || sep == null) {
             return null;
@@ -643,7 +552,7 @@
             }
         }
 
-        return new Range(TextUtils.concat(before, after), startIndexInBefore,
+        return new TextRange(TextUtils.concat(before, after), startIndexInBefore,
                 before.length() + endIndexInAfter, before.length());
     }
 
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index f52e564..8c41cf8 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -42,8 +42,8 @@
 import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
 import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.FeedbackUtils;
-import com.android.inputmethod.latin.utils.Utils;
 import com.android.inputmethod.research.ResearchLogger;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
@@ -90,7 +90,7 @@
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
             preferenceScreen.setTitle(
-                    Utils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
+                    ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class));
         }
 
         final Resources res = getResources();
diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java
index 5b47dda..22beaef 100644
--- a/java/src/com/android/inputmethod/latin/SuggestedWords.java
+++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java
@@ -75,6 +75,21 @@
         return mSuggestedWordInfoList.get(index);
     }
 
+    public String getDebugString(final int pos) {
+        if (!LatinImeLogger.sDBG) {
+            return null;
+        }
+        final SuggestedWordInfo wordInfo = getInfo(pos);
+        if (wordInfo == null) {
+            return null;
+        }
+        final String debugString = wordInfo.getDebugString();
+        if (TextUtils.isEmpty(debugString)) {
+            return null;
+        }
+        return debugString;
+    }
+
     public boolean willAutoCorrect() {
         return mWillAutoCorrect;
     }
diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
index ba84c1a..ab8f348 100644
--- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java
@@ -240,7 +240,6 @@
 
     private void addWords(final Cursor cursor) {
         final boolean hasShortcutColumn = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
-        clearFusionDictionary();
         if (cursor == null) return;
         if (cursor.moveToFirst()) {
             final int indexWord = cursor.getColumnIndex(Words.WORD);
@@ -267,4 +266,9 @@
     protected boolean hasContentChanged() {
         return true;
     }
+
+    @Override
+    protected boolean needsToReloadBeforeWriting() {
+        return true;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index ca2d884..8c668b8 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -320,7 +320,11 @@
                     mUserHistoryDictionary.mBigramListLock.unlock();
                 }
             } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) {
-                doWriteTaskLocked();
+                try {
+                    doWriteTaskLocked();
+                } finally {
+                    mUserHistoryDictionary.mBigramListLock.unlock();
+                }
             }
             return null;
         }
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index e078f03..2babe8b 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -192,6 +192,40 @@
         return mCursorPositionWithinWord != mCodePointSize;
     }
 
+    /**
+     * When the cursor is moved by the user, we need to update its position.
+     * If it falls inside the currently composing word, we don't reset the composition, and
+     * only update the cursor position.
+     *
+     * @param expectedMoveAmount How many java chars to move the cursor. Negative values move
+     * the cursor backward, positive values move the cursor forward.
+     * @return true if the cursor is still inside the composing word, false otherwise.
+     */
+    public boolean moveCursorByAndReturnIfInsideComposingWord(final int expectedMoveAmount) {
+        int actualMoveAmountWithinWord = 0;
+        int cursorPos = mCursorPositionWithinWord;
+        if (expectedMoveAmount >= 0) {
+            // Moving the cursor forward for the expected amount or until the end of the word has
+            // been reached, whichever comes first.
+            while (actualMoveAmountWithinWord < expectedMoveAmount && cursorPos < mCodePointSize) {
+                actualMoveAmountWithinWord += Character.charCount(mPrimaryKeyCodes[cursorPos]);
+                ++cursorPos;
+            }
+        } else {
+            // Moving the cursor backward for the expected amount or until the start of the word
+            // has been reached, whichever comes first.
+            while (actualMoveAmountWithinWord > expectedMoveAmount && cursorPos > 0) {
+                --cursorPos;
+                actualMoveAmountWithinWord -= Character.charCount(mPrimaryKeyCodes[cursorPos]);
+            }
+        }
+        // If the actual and expected amounts differ, we crossed the start or the end of the word
+        // so the result would not be inside the composing word.
+        if (actualMoveAmountWithinWord != expectedMoveAmount) return false;
+        mCursorPositionWithinWord = cursorPos;
+        return true;
+    }
+
     public void setBatchInputPointers(final InputPointers batchPointers) {
         mInputPointers.set(batchPointers);
         mIsBatchMode = true;
diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
index da58126..999ca77 100644
--- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java
@@ -21,7 +21,7 @@
 import android.preference.PreferenceScreen;
 
 import com.android.inputmethod.latin.R;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ApplicationUtils;
 
 /**
  * Preference screen.
@@ -39,7 +39,7 @@
         addPreferencesFromResource(R.xml.spell_checker_settings);
         final PreferenceScreen preferenceScreen = getPreferenceScreen();
         if (preferenceScreen != null) {
-            preferenceScreen.setTitle(Utils.getAcitivityTitleResId(
+            preferenceScreen.setTitle(ApplicationUtils.getAcitivityTitleResId(
                     getActivity(), SpellCheckerSettingsActivity.class));
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index 2218b3b..e97069d 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -24,14 +24,13 @@
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
-import com.android.inputmethod.keyboard.TypefaceUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.TypefaceUtils;
 
 public final class MoreSuggestions extends Keyboard {
     public static final int SUGGESTION_CODE_BASE = 1024;
@@ -207,7 +206,7 @@
                 final int y = params.getY(index);
                 final int width = params.getWidth(index);
                 final String word = mSuggestedWords.getWord(index);
-                final String info = Utils.getDebugInfo(mSuggestedWords, index);
+                final String info = mSuggestedWords.getDebugString(index);
                 final int indexInMoreSuggestions = index + SUGGESTION_CODE_BASE;
                 final Key key = new Key(
                         params, word, info, KeyboardIconsSet.ICON_UNDEFINED, indexInMoreSuggestions,
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
index 9565f63..ce340b6 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java
@@ -45,13 +45,12 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.inputmethod.keyboard.ViewLayoutUtils;
 import com.android.inputmethod.latin.AutoCorrection;
 import com.android.inputmethod.latin.LatinImeLogger;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.utils.ResourceUtils;
-import com.android.inputmethod.latin.utils.Utils;
+import com.android.inputmethod.latin.utils.ViewLayoutUtils;
 
 import java.util.ArrayList;
 
@@ -446,7 +445,7 @@
             wordView.setTextColor(getSuggestionTextColor(positionInStrip, suggestedWords));
             if (SuggestionStripView.DBG) {
                 mDebugInfoViews.get(positionInStrip).setText(
-                        Utils.getDebugInfo(suggestedWords, indexInSuggestedWords));
+                        suggestedWords.getDebugString(indexInSuggestedWords));
             }
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
new file mode 100644
index 0000000..08a2a8c
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/ApplicationUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+public final class ApplicationUtils {
+    private static final String TAG = ApplicationUtils.class.getSimpleName();
+
+    private ApplicationUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static int getAcitivityTitleResId(final Context context,
+            final Class<? extends Activity> cls) {
+        final ComponentName cn = new ComponentName(context, cls);
+        try {
+            final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
+            if (ai != null) {
+                return ai.labelRes;
+            }
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Failed to get settings activity title res id.", e);
+        }
+        return 0;
+    }
+
+    /**
+     * A utility method to get the application's PackageInfo.versionName
+     * @return the application's PackageInfo.versionName
+     */
+    public static String getVersionName(final Context context) {
+        try {
+            if (context == null) {
+                return "";
+            }
+            final String packageName = context.getPackageName();
+            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
+            return info.versionName;
+        } catch (final NameNotFoundException e) {
+            Log.e(TAG, "Could not find version info.", e);
+        }
+        return "";
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
index b3d37d7..34eccd6 100644
--- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.res.AssetManager;
 import android.content.res.Resources;
-import android.text.format.DateUtils;
 import android.util.Log;
 
 import com.android.inputmethod.latin.AssetFileAddress;
@@ -35,6 +34,7 @@
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class encapsulates the logic for the Latin-IME side of dictionary information management.
@@ -74,8 +74,8 @@
             values.put(LOCALE_COLUMN, mLocale.toString());
             values.put(DESCRIPTION_COLUMN, mDescription);
             values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename);
-            values.put(DATE_COLUMN,
-                    new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS);
+            values.put(DATE_COLUMN, TimeUnit.MILLISECONDS.toSeconds(
+                    new File(mFileAddress.mFilename).lastModified()));
             values.put(FILESIZE_COLUMN, mFileAddress.mLength);
             values.put(VERSION_COLUMN, mVersion);
             return values;
diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
new file mode 100644
index 0000000..e958a7e
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.text.TextUtils;
+
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.LatinImeLogger;
+import com.android.inputmethod.latin.WordComposer;
+
+public final class LatinImeLoggerUtils {
+    private LatinImeLoggerUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void onNonSeparator(final char code, final int x, final int y) {
+        UserLogRingCharBuffer.getInstance().push(code, x, y);
+        LatinImeLogger.logOnInputChar();
+    }
+
+    public static void onSeparator(final int code, final int x, final int y) {
+        // Helper method to log a single code point separator
+        // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
+        onSeparator(new String(new int[]{code}, 0, 1), x, y);
+    }
+
+    public static void onSeparator(final String separator, final int x, final int y) {
+        final int length = separator.length();
+        for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
+            int codePoint = Character.codePointAt(separator, i);
+            // TODO: accept code points
+            UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
+        }
+        LatinImeLogger.logOnInputSeparator();
+    }
+
+    public static void onAutoCorrection(final String typedWord, final String correctedWord,
+            final String separatorString, final WordComposer wordComposer) {
+        final boolean isBatchMode = wordComposer.isBatchMode();
+        if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
+            return;
+        }
+        // TODO: this fails when the separator is more than 1 code point long, but
+        // the backend can't handle it yet. The only case when this happens is with
+        // smileys and other multi-character keys.
+        final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
+                : separatorString.codePointAt(0);
+        if (!isBatchMode) {
+            LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
+        } else {
+            if (!TextUtils.isEmpty(correctedWord)) {
+                // We must make sure that InputPointer contains only the relative timestamps,
+                // not actual timestamps.
+                LatinImeLogger.logOnAutoCorrectionForGeometric(
+                        "", correctedWord, codePoint, wordComposer.getInputPointers());
+            }
+        }
+    }
+
+    public static void onAutoCorrectionCancellation() {
+        LatinImeLogger.logOnAutoCorrectionCancelled();
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/TextRange.java b/java/src/com/android/inputmethod/latin/utils/TextRange.java
new file mode 100644
index 0000000..5793e41
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/TextRange.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.text.Spanned;
+import android.text.style.SuggestionSpan;
+
+import java.util.Arrays;
+
+/**
+ * Represents a range of text, relative to the current cursor position.
+ */
+public final class TextRange {
+    private final CharSequence mTextAtCursor;
+    private final int mWordAtCursorStartIndex;
+    private final int mWordAtCursorEndIndex;
+    private final int mCursorIndex;
+
+    public final CharSequence mWord;
+
+    public int getNumberOfCharsInWordBeforeCursor() {
+        return mCursorIndex - mWordAtCursorStartIndex;
+    }
+
+    public int getNumberOfCharsInWordAfterCursor() {
+        return mWordAtCursorEndIndex - mCursorIndex;
+    }
+
+    /**
+     * Gets the suggestion spans that are put squarely on the word, with the exact start
+     * and end of the span matching the boundaries of the word.
+     * @return the list of spans.
+     */
+    public SuggestionSpan[] getSuggestionSpansAtWord() {
+        if (!(mTextAtCursor instanceof Spanned && mWord instanceof Spanned)) {
+            return new SuggestionSpan[0];
+        }
+        final Spanned text = (Spanned)mTextAtCursor;
+        // Note: it's fine to pass indices negative or greater than the length of the string
+        // to the #getSpans() method. The reason we need to get from -1 to +1 is that, the
+        // spans were cut at the cursor position, and #getSpans(start, end) does not return
+        // spans that end at `start' or begin at `end'. Consider the following case:
+        //              this| is          (The | symbolizes the cursor position
+        //              ---- ---
+        // In this case, the cursor is in position 4, so the 0~7 span has been split into
+        // a 0~4 part and a 4~7 part.
+        // If we called #getSpans(0, 4) in this case, we would only get the part from 0 to 4
+        // of the span, and not the part from 4 to 7, so we would not realize the span actually
+        // extends from 0 to 7. But if we call #getSpans(-1, 5) we'll get both the 0~4 and
+        // the 4~7 spans and we can merge them accordingly.
+        // Any span starting more than 1 char away from the word boundaries in any direction
+        // does not touch the word, so we don't need to consider it. That's why requesting
+        // -1 ~ +1 is enough.
+        // Of course this is only relevant if the cursor is at one end of the word. If it's
+        // in the middle, the -1 and +1 are not necessary, but they are harmless.
+        final SuggestionSpan[] spans = text.getSpans(mWordAtCursorStartIndex - 1,
+                mWordAtCursorEndIndex + 1, SuggestionSpan.class);
+        int readIndex = 0;
+        int writeIndex = 0;
+        for (; readIndex < spans.length; ++readIndex) {
+            final SuggestionSpan span = spans[readIndex];
+            // The span may be null, as we null them when we find duplicates. Cf a few lines
+            // down.
+            if (null == span) continue;
+            // Tentative span start and end. This may be modified later if we realize the
+            // same span is also applied to other parts of the string.
+            int spanStart = text.getSpanStart(span);
+            int spanEnd = text.getSpanEnd(span);
+            for (int i = readIndex + 1; i < spans.length; ++i) {
+                if (span.equals(spans[i])) {
+                    // We found the same span somewhere else. Read the new extent of this
+                    // span, and adjust our values accordingly.
+                    spanStart = Math.min(spanStart, text.getSpanStart(spans[i]));
+                    spanEnd = Math.max(spanEnd, text.getSpanEnd(spans[i]));
+                    // ...and mark the span as processed.
+                    spans[i] = null;
+                }
+            }
+            if (spanStart == mWordAtCursorStartIndex && spanEnd == mWordAtCursorEndIndex) {
+                // If the span does not start and stop here, we ignore it. It probably extends
+                // past the start or end of the word, as happens in missing space correction
+                // or EasyEditSpans put by voice input.
+                spans[writeIndex++] = spans[readIndex];
+            }
+        }
+        return writeIndex == readIndex ? spans : Arrays.copyOfRange(spans, 0, writeIndex);
+    }
+
+    public TextRange(final CharSequence textAtCursor, final int wordAtCursorStartIndex,
+            final int wordAtCursorEndIndex, final int cursorIndex) {
+        if (wordAtCursorStartIndex < 0 || cursorIndex < wordAtCursorStartIndex
+                || cursorIndex > wordAtCursorEndIndex
+                || wordAtCursorEndIndex > textAtCursor.length()) {
+            throw new IndexOutOfBoundsException();
+        }
+        mTextAtCursor = textAtCursor;
+        mWordAtCursorStartIndex = wordAtCursorStartIndex;
+        mWordAtCursorEndIndex = wordAtCursorEndIndex;
+        mCursorIndex = cursorIndex;
+        mWord = mTextAtCursor.subSequence(mWordAtCursorStartIndex, mWordAtCursorEndIndex);
+    }
+}
\ No newline at end of file
diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
similarity index 96%
rename from java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
rename to java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
index c3b9520..544e4d2 100644
--- a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java
@@ -14,15 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
 import android.util.SparseArray;
 
-import com.android.inputmethod.latin.utils.CollectionUtils;
-
 public final class TypefaceUtils {
     private TypefaceUtils() {
         // This utility class is not publicly instantiable.
diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
new file mode 100644
index 0000000..ef9cacf
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.inputmethodservice.InputMethodService;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.util.Log;
+
+import com.android.inputmethod.latin.LatinImeLogger;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public final class UsabilityStudyLogUtils {
+    // TODO: remove code duplication with ResearchLog class
+    private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
+    private static final String FILENAME = "log.txt";
+    private final Handler mLoggingHandler;
+    private File mFile;
+    private File mDirectory;
+    private InputMethodService mIms;
+    private PrintWriter mWriter;
+    private final Date mDate;
+    private final SimpleDateFormat mDateFormat;
+
+    private UsabilityStudyLogUtils() {
+        mDate = new Date();
+        mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
+
+        HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        handlerThread.start();
+        mLoggingHandler = new Handler(handlerThread.getLooper());
+    }
+
+    // Initialization-on-demand holder
+    private static final class OnDemandInitializationHolder {
+        public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
+    }
+
+    public static UsabilityStudyLogUtils getInstance() {
+        return OnDemandInitializationHolder.sInstance;
+    }
+
+    public void init(final InputMethodService ims) {
+        mIms = ims;
+        mDirectory = ims.getFilesDir();
+    }
+
+    private void createLogFileIfNotExist() {
+        if ((mFile == null || !mFile.exists())
+                && (mDirectory != null && mDirectory.exists())) {
+            try {
+                mWriter = getPrintWriter(mDirectory, FILENAME, false);
+            } catch (final IOException e) {
+                Log.e(USABILITY_TAG, "Can't create log file.");
+            }
+        }
+    }
+
+    public static void writeBackSpace(final int x, final int y) {
+        UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
+    }
+
+    public static void writeChar(final char c, final int x, final int y) {
+        String inputChar = String.valueOf(c);
+        switch (c) {
+            case '\n':
+                inputChar = "<enter>";
+                break;
+            case '\t':
+                inputChar = "<tab>";
+                break;
+            case ' ':
+                inputChar = "<space>";
+                break;
+        }
+        UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
+        LatinImeLogger.onPrintAllUsabilityStudyLogs();
+    }
+
+    public void write(final String log) {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                createLogFileIfNotExist();
+                final long currentTime = System.currentTimeMillis();
+                mDate.setTime(currentTime);
+
+                final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
+                        mDateFormat.format(mDate), currentTime, log);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Write: " + log);
+                }
+                mWriter.print(printString);
+            }
+        });
+    }
+
+    private synchronized String getBufferedLogs() {
+        mWriter.flush();
+        final StringBuilder sb = new StringBuilder();
+        final BufferedReader br = getBufferedReader();
+        String line;
+        try {
+            while ((line = br.readLine()) != null) {
+                sb.append('\n');
+                sb.append(line);
+            }
+        } catch (final IOException e) {
+            Log.e(USABILITY_TAG, "Can't read log file.");
+        } finally {
+            if (LatinImeLogger.sDBG) {
+                Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
+            }
+            try {
+                br.close();
+            } catch (final IOException e) {
+                // ignore.
+            }
+        }
+        return sb.toString();
+    }
+
+    public void emailResearcherLogsAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                final Date date = new Date();
+                date.setTime(System.currentTimeMillis());
+                final String currentDateTimeString =
+                        new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
+                if (mFile == null) {
+                    Log.w(USABILITY_TAG, "No internal log file found.");
+                    return;
+                }
+                if (mIms.checkCallingOrSelfPermission(
+                            android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+                                    != PackageManager.PERMISSION_GRANTED) {
+                    Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
+                    return;
+                }
+                mWriter.flush();
+                final String destPath = Environment.getExternalStorageDirectory()
+                        + "/research-" + currentDateTimeString + ".log";
+                final File destFile = new File(destPath);
+                try {
+                    final FileInputStream srcStream = new FileInputStream(mFile);
+                    final FileOutputStream destStream = new FileOutputStream(destFile);
+                    final FileChannel src = srcStream.getChannel();
+                    final FileChannel dest = destStream.getChannel();
+                    src.transferTo(0, src.size(), dest);
+                    src.close();
+                    srcStream.close();
+                    dest.close();
+                    destStream.close();
+                } catch (final FileNotFoundException e1) {
+                    Log.w(USABILITY_TAG, e1);
+                    return;
+                } catch (final IOException e2) {
+                    Log.w(USABILITY_TAG, e2);
+                    return;
+                }
+                if (destFile == null || !destFile.exists()) {
+                    Log.w(USABILITY_TAG, "Dest file doesn't exist.");
+                    return;
+                }
+                final Intent intent = new Intent(Intent.ACTION_SEND);
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                if (LatinImeLogger.sDBG) {
+                    Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
+                }
+                intent.setType("text/plain");
+                intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
+                intent.putExtra(Intent.EXTRA_SUBJECT,
+                        "[Research Logs] " + currentDateTimeString);
+                mIms.startActivity(intent);
+            }
+        });
+    }
+
+    public void printAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
+            }
+        });
+    }
+
+    public void clearAll() {
+        mLoggingHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mFile != null && mFile.exists()) {
+                    if (LatinImeLogger.sDBG) {
+                        Log.d(USABILITY_TAG, "Delete log file.");
+                    }
+                    mFile.delete();
+                    mWriter.close();
+                }
+            }
+        });
+    }
+
+    private BufferedReader getBufferedReader() {
+        createLogFileIfNotExist();
+        try {
+            return new BufferedReader(new FileReader(mFile));
+        } catch (final FileNotFoundException e) {
+            return null;
+        }
+    }
+
+    private PrintWriter getPrintWriter(final File dir, final String filename,
+            final boolean renew) throws IOException {
+        mFile = new File(dir, filename);
+        if (mFile.exists()) {
+            if (renew) {
+                mFile.delete();
+            }
+        }
+        return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
index 9f842f9..713a45b 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java
@@ -16,9 +16,10 @@
 
 package com.android.inputmethod.latin.utils;
 
-import android.text.format.DateUtils;
 import android.util.Log;
 
+import java.util.concurrent.TimeUnit;
+
 public final class UserHistoryForgettingCurveUtils {
     private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
@@ -27,8 +28,8 @@
     private static final int FC_LEVEL_MAX = 3;
     /* package */ static final int ELAPSED_TIME_MAX = 15;
     private static final int ELAPSED_TIME_INTERVAL_HOURS = 6;
-    private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS
-            * DateUtils.HOUR_IN_MILLIS;
+    private static final long ELAPSED_TIME_INTERVAL_MILLIS =
+            TimeUnit.HOURS.toMillis(ELAPSED_TIME_INTERVAL_HOURS);
     private static final int HALF_LIFE_HOURS = 48;
     private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1);
 
diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
index 3e67e82..a2c6c45 100644
--- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
+++ b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java
@@ -20,7 +20,6 @@
 
 import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Settings;
-import com.android.inputmethod.latin.utils.Utils.UsabilityStudyLogUtils;
 
 public final class UserLogRingCharBuffer {
     public /* for test */ static final int BUFSIZE = 20;
diff --git a/java/src/com/android/inputmethod/latin/utils/Utils.java b/java/src/com/android/inputmethod/latin/utils/Utils.java
deleted file mode 100644
index c4e18ed..0000000
--- a/java/src/com/android/inputmethod/latin/utils/Utils.java
+++ /dev/null
@@ -1,374 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.inputmethod.latin.utils;
-
-import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.inputmethodservice.InputMethodService;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.inputmethod.latin.Constants;
-import com.android.inputmethod.latin.LatinImeLogger;
-import com.android.inputmethod.latin.SuggestedWords;
-import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
-import com.android.inputmethod.latin.WordComposer;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.channels.FileChannel;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-
-// TODO: Come up with a more descriptive class name
-public final class Utils {
-    private static final String TAG = Utils.class.getSimpleName();
-
-    private Utils() {
-        // This utility class is not publicly instantiable.
-    }
-
-    // TODO: Make this an external class
-    public static final class UsabilityStudyLogUtils {
-        // TODO: remove code duplication with ResearchLog class
-        private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName();
-        private static final String FILENAME = "log.txt";
-        private final Handler mLoggingHandler;
-        private File mFile;
-        private File mDirectory;
-        private InputMethodService mIms;
-        private PrintWriter mWriter;
-        private final Date mDate;
-        private final SimpleDateFormat mDateFormat;
-
-        private UsabilityStudyLogUtils() {
-            mDate = new Date();
-            mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US);
-
-            HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task",
-                    Process.THREAD_PRIORITY_BACKGROUND);
-            handlerThread.start();
-            mLoggingHandler = new Handler(handlerThread.getLooper());
-        }
-
-        // Initialization-on-demand holder
-        private static final class OnDemandInitializationHolder {
-            public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils();
-        }
-
-        public static UsabilityStudyLogUtils getInstance() {
-            return OnDemandInitializationHolder.sInstance;
-        }
-
-        public void init(final InputMethodService ims) {
-            mIms = ims;
-            mDirectory = ims.getFilesDir();
-        }
-
-        private void createLogFileIfNotExist() {
-            if ((mFile == null || !mFile.exists())
-                    && (mDirectory != null && mDirectory.exists())) {
-                try {
-                    mWriter = getPrintWriter(mDirectory, FILENAME, false);
-                } catch (final IOException e) {
-                    Log.e(USABILITY_TAG, "Can't create log file.");
-                }
-            }
-        }
-
-        public static void writeBackSpace(final int x, final int y) {
-            UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y);
-        }
-
-        public static void writeChar(final char c, final int x, final int y) {
-            String inputChar = String.valueOf(c);
-            switch (c) {
-                case '\n':
-                    inputChar = "<enter>";
-                    break;
-                case '\t':
-                    inputChar = "<tab>";
-                    break;
-                case ' ':
-                    inputChar = "<space>";
-                    break;
-            }
-            UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y);
-            LatinImeLogger.onPrintAllUsabilityStudyLogs();
-        }
-
-        public void write(final String log) {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    createLogFileIfNotExist();
-                    final long currentTime = System.currentTimeMillis();
-                    mDate.setTime(currentTime);
-
-                    final String printString = String.format(Locale.US, "%s\t%d\t%s\n",
-                            mDateFormat.format(mDate), currentTime, log);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Write: " + log);
-                    }
-                    mWriter.print(printString);
-                }
-            });
-        }
-
-        private synchronized String getBufferedLogs() {
-            mWriter.flush();
-            final StringBuilder sb = new StringBuilder();
-            final BufferedReader br = getBufferedReader();
-            String line;
-            try {
-                while ((line = br.readLine()) != null) {
-                    sb.append('\n');
-                    sb.append(line);
-                }
-            } catch (final IOException e) {
-                Log.e(USABILITY_TAG, "Can't read log file.");
-            } finally {
-                if (LatinImeLogger.sDBG) {
-                    Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString());
-                }
-                try {
-                    br.close();
-                } catch (final IOException e) {
-                    // ignore.
-                }
-            }
-            return sb.toString();
-        }
-
-        public void emailResearcherLogsAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final Date date = new Date();
-                    date.setTime(System.currentTimeMillis());
-                    final String currentDateTimeString =
-                            new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date);
-                    if (mFile == null) {
-                        Log.w(USABILITY_TAG, "No internal log file found.");
-                        return;
-                    }
-                    if (mIms.checkCallingOrSelfPermission(
-                                android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
-                                        != PackageManager.PERMISSION_GRANTED) {
-                        Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE");
-                        return;
-                    }
-                    mWriter.flush();
-                    final String destPath = Environment.getExternalStorageDirectory()
-                            + "/research-" + currentDateTimeString + ".log";
-                    final File destFile = new File(destPath);
-                    try {
-                        final FileInputStream srcStream = new FileInputStream(mFile);
-                        final FileOutputStream destStream = new FileOutputStream(destFile);
-                        final FileChannel src = srcStream.getChannel();
-                        final FileChannel dest = destStream.getChannel();
-                        src.transferTo(0, src.size(), dest);
-                        src.close();
-                        srcStream.close();
-                        dest.close();
-                        destStream.close();
-                    } catch (final FileNotFoundException e1) {
-                        Log.w(USABILITY_TAG, e1);
-                        return;
-                    } catch (final IOException e2) {
-                        Log.w(USABILITY_TAG, e2);
-                        return;
-                    }
-                    if (destFile == null || !destFile.exists()) {
-                        Log.w(USABILITY_TAG, "Dest file doesn't exist.");
-                        return;
-                    }
-                    final Intent intent = new Intent(Intent.ACTION_SEND);
-                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    if (LatinImeLogger.sDBG) {
-                        Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI());
-                    }
-                    intent.setType("text/plain");
-                    intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath));
-                    intent.putExtra(Intent.EXTRA_SUBJECT,
-                            "[Research Logs] " + currentDateTimeString);
-                    mIms.startActivity(intent);
-                }
-            });
-        }
-
-        public void printAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0);
-                }
-            });
-        }
-
-        public void clearAll() {
-            mLoggingHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    if (mFile != null && mFile.exists()) {
-                        if (LatinImeLogger.sDBG) {
-                            Log.d(USABILITY_TAG, "Delete log file.");
-                        }
-                        mFile.delete();
-                        mWriter.close();
-                    }
-                }
-            });
-        }
-
-        private BufferedReader getBufferedReader() {
-            createLogFileIfNotExist();
-            try {
-                return new BufferedReader(new FileReader(mFile));
-            } catch (final FileNotFoundException e) {
-                return null;
-            }
-        }
-
-        private PrintWriter getPrintWriter(final File dir, final String filename,
-                final boolean renew) throws IOException {
-            mFile = new File(dir, filename);
-            if (mFile.exists()) {
-                if (renew) {
-                    mFile.delete();
-                }
-            }
-            return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */);
-        }
-    }
-
-    // TODO: Make this an external class
-    public static final class Stats {
-        public static void onNonSeparator(final char code, final int x, final int y) {
-            UserLogRingCharBuffer.getInstance().push(code, x, y);
-            LatinImeLogger.logOnInputChar();
-        }
-
-        public static void onSeparator(final int code, final int x, final int y) {
-            // Helper method to log a single code point separator
-            // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils
-            onSeparator(new String(new int[]{code}, 0, 1), x, y);
-        }
-
-        public static void onSeparator(final String separator, final int x, final int y) {
-            final int length = separator.length();
-            for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) {
-                int codePoint = Character.codePointAt(separator, i);
-                // TODO: accept code points
-                UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y);
-            }
-            LatinImeLogger.logOnInputSeparator();
-        }
-
-        public static void onAutoCorrection(final String typedWord, final String correctedWord,
-                final String separatorString, final WordComposer wordComposer) {
-            final boolean isBatchMode = wordComposer.isBatchMode();
-            if (!isBatchMode && TextUtils.isEmpty(typedWord)) {
-                return;
-            }
-            // TODO: this fails when the separator is more than 1 code point long, but
-            // the backend can't handle it yet. The only case when this happens is with
-            // smileys and other multi-character keys.
-            final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE
-                    : separatorString.codePointAt(0);
-            if (!isBatchMode) {
-                LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint);
-            } else {
-                if (!TextUtils.isEmpty(correctedWord)) {
-                    // We must make sure that InputPointer contains only the relative timestamps,
-                    // not actual timestamps.
-                    LatinImeLogger.logOnAutoCorrectionForGeometric(
-                            "", correctedWord, codePoint, wordComposer.getInputPointers());
-                }
-            }
-        }
-
-        public static void onAutoCorrectionCancellation() {
-            LatinImeLogger.logOnAutoCorrectionCancelled();
-        }
-    }
-
-    public static String getDebugInfo(final SuggestedWords suggestions, final int pos) {
-        if (!LatinImeLogger.sDBG) {
-            return null;
-        }
-        final SuggestedWordInfo wordInfo = suggestions.getInfo(pos);
-        if (wordInfo == null) {
-            return null;
-        }
-        final String info = wordInfo.getDebugString();
-        if (TextUtils.isEmpty(info)) {
-            return null;
-        }
-        return info;
-    }
-
-    public static int getAcitivityTitleResId(final Context context,
-            final Class<? extends Activity> cls) {
-        final ComponentName cn = new ComponentName(context, cls);
-        try {
-            final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0);
-            if (ai != null) {
-                return ai.labelRes;
-            }
-        } catch (final NameNotFoundException e) {
-            Log.e(TAG, "Failed to get settings activity title res id.", e);
-        }
-        return 0;
-    }
-
-    /**
-     * A utility method to get the application's PackageInfo.versionName
-     * @return the application's PackageInfo.versionName
-     */
-    public static String getVersionName(final Context context) {
-        try {
-            if (context == null) {
-                return "";
-            }
-            final String packageName = context.getPackageName();
-            final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
-            return info.versionName;
-        } catch (final NameNotFoundException e) {
-            Log.e(TAG, "Could not find version info.", e);
-        }
-        return "";
-    }
-}
diff --git a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
similarity index 86%
rename from java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
rename to java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
index dc12fa4..f9d8534 100644
--- a/java/src/com/android/inputmethod/keyboard/ViewLayoutUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/ViewLayoutUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.inputmethod.keyboard;
+package com.android.inputmethod.latin.utils;
 
 import android.view.View;
 import android.view.ViewGroup;
@@ -27,7 +27,8 @@
         // This utility class is not publicly instantiable.
     }
 
-    public static MarginLayoutParams newLayoutParam(ViewGroup placer, int width, int height) {
+    public static MarginLayoutParams newLayoutParam(final ViewGroup placer, final int width,
+            final int height) {
         if (placer instanceof FrameLayout) {
             return new FrameLayout.LayoutParams(width, height);
         } else if (placer instanceof RelativeLayout) {
@@ -40,7 +41,8 @@
         }
     }
 
-    public static void placeViewAt(View view, int x, int y, int w, int h) {
+    public static void placeViewAt(final View view, final int x, final int y, final int w,
+            final int h) {
         final ViewGroup.LayoutParams lp = view.getLayoutParams();
         if (lp instanceof MarginLayoutParams) {
             final MarginLayoutParams marginLayoutParams = (MarginLayoutParams)lp;
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 88207c0..46e620a 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -55,7 +55,7 @@
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false
             && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG;
-    private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
+    private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5);
 
     /* package */ final ScheduledExecutorService mExecutor;
     /* package */ final File mFile;
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index ecf8092..ed047e1 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -37,7 +37,6 @@
 import android.os.SystemClock;
 import android.preference.PreferenceManager;
 import android.text.TextUtils;
-import android.text.format.DateUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -57,11 +56,11 @@
 import com.android.inputmethod.latin.LatinIME;
 import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.RichInputConnection;
-import com.android.inputmethod.latin.RichInputConnection.Range;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.SuggestedWords;
 import com.android.inputmethod.latin.define.ProductionFlag;
 import com.android.inputmethod.latin.utils.InputTypeUtils;
+import com.android.inputmethod.latin.utils.TextRange;
 import com.android.inputmethod.research.MotionEventReader.ReplayData;
 import com.android.inputmethod.research.ui.SplashScreen;
 
@@ -75,6 +74,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 // TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls
@@ -137,10 +137,10 @@
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
     private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
-    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = 5 * 1000;
-    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = DateUtils.DAY_IN_MILLIS;
-    private static final long MAX_LOGFILE_AGE_IN_MS = 4 * DateUtils.DAY_IN_MILLIS;
+    private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5);
+    private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1);
+    private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4);
 
     private static final ResearchLogger sInstance = new ResearchLogger();
     private static String sAccountType = null;
@@ -195,7 +195,7 @@
     // not performed on text that the user types into the feedback dialog.
     private boolean mInFeedbackDialog = false;
     private Handler mUserRecordingTimeoutHandler;
-    private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS;
+    private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
 
     // Stores a temporary LogUnit while generating a phantom space.  Needed because phantom spaces
     // are issued out-of-order, immediately before the characters generated by other operations that
@@ -542,8 +542,8 @@
             toast.show();
             boolean isLogDeleted = abort();
             final long currentTime = System.currentTimeMillis();
-            final long resumeTime = currentTime + 1000 * 60 *
-                    SUSPEND_DURATION_IN_MINUTES;
+            final long resumeTime = currentTime
+                    + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES);
             suspendLoggingUntil(resumeTime);
             toast.cancel();
             Toast.makeText(latinIME, R.string.research_notify_logging_suspended,
@@ -635,7 +635,7 @@
                             mMotionEventReader.readMotionEventData(mUserRecordingFile);
                     mReplayer.replay(replayData, null);
                 }
-            }, 1000);
+            }, TimeUnit.SECONDS.toMillis(1));
         }
 
         if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) {
@@ -1220,7 +1220,7 @@
             final RichInputConnection connection) {
         String word = "";
         if (connection != null) {
-            Range range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
+            TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1);
             if (range != null) {
                 word = range.mWord.toString();
             }
diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java
index e573ca0..fd323a1 100644
--- a/java/src/com/android/inputmethod/research/Statistics.java
+++ b/java/src/com/android/inputmethod/research/Statistics.java
@@ -21,6 +21,8 @@
 import com.android.inputmethod.latin.Constants;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
+import java.util.concurrent.TimeUnit;
+
 public class Statistics {
     private static final String TAG = Statistics.class.getSimpleName();
     private static final boolean DEBUG = false
@@ -102,8 +104,8 @@
 
     // To account for the interruptions when the user's attention is directed elsewhere, times
     // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic.
-    public static final int MIN_TYPING_INTERMISSION = 2 * 1000;  // in milliseconds
-    public static final int MIN_DELETION_INTERMISSION = 10 * 1000;  // in milliseconds
+    public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2);
+    public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10);
 
     // The last time that a tap was performed
     private long mLastTapTime;
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 8bd46c1..fd3f2f6 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -34,7 +34,6 @@
     public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
     public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
             + ".extra.UPLOAD_UNCONDITIONALLY";
-    protected static final int TIMEOUT_IN_MS = 1000 * 4;
 
     public UploaderService() {
         super("Research Uploader Service");
diff --git a/native/jni/Android.mk b/native/jni/Android.mk
index f89eea7..d78da96 100644
--- a/native/jni/Android.mk
+++ b/native/jni/Android.mk
@@ -79,8 +79,9 @@
         typing_traversal.cpp \
         typing_weighting.cpp) \
     $(addprefix utils/, \
+        autocorrection_threshold_utils.cpp \
         char_utils.cpp \
-        autocorrection_threshold_utils.cpp)
+        log_utils.cpp)
 
 LOCAL_SRC_FILES := \
     $(LATIN_IME_JNI_SRC_FILES) \
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index a93bbeb..6e1b80e 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -93,8 +93,8 @@
         AKLOGE("DICT: dictionary format is unknown, bad magic number");
         releaseDictBuf(static_cast<const char *>(dictBuf) - offset, adjDictSize, fd);
     } else {
-        dictionary = new Dictionary(
-                dictBuf, static_cast<int>(dictSize), fd, offset, updatableMmap);
+        dictionary = new Dictionary(env, dictBuf, static_cast<int>(dictSize), fd, offset,
+                updatableMmap);
     }
     PROF_END(66);
     PROF_CLOSE;
@@ -201,7 +201,7 @@
 static jint latinime_BinaryDictionary_getProbability(JNIEnv *env, jclass clazz, jlong dict,
         jintArray word) {
     Dictionary *dictionary = reinterpret_cast<Dictionary *>(dict);
-    if (!dictionary) return 0;
+    if (!dictionary) return NOT_A_PROBABILITY;
     const jsize wordLength = env->GetArrayLength(word);
     int codePoints[wordLength];
     env->GetIntArrayRegion(word, 0, wordLength, codePoints);
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index cb66814..607a744 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -35,6 +35,56 @@
 // Must be equal to ProximityInfo.MAX_PROXIMITY_CHARS_SIZE in Java
 #define MAX_PROXIMITY_CHARS_SIZE 16
 #define ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE 2
+#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
+
+AK_FORCE_INLINE static int intArrayToCharArray(const int *const source, const int sourceSize,
+        char *dest, const int destSize) {
+    // We want to always terminate with a 0 char, so stop one short of the length to make
+    // sure there is room.
+    const int destLimit = destSize - 1;
+    int si = 0;
+    int di = 0;
+    while (si < sourceSize && di < destLimit && 0 != source[si]) {
+        const int codePoint = source[si++];
+        if (codePoint < 0x7F) { // One byte
+            dest[di++] = codePoint;
+        } else if (codePoint < 0x7FF) { // Two bytes
+            if (di + 1 >= destLimit) break;
+            dest[di++] = 0xC0 + (codePoint >> 6);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint < 0xFFFF) { // Three bytes
+            if (di + 2 >= destLimit) break;
+            dest[di++] = 0xE0 + (codePoint >> 12);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x1FFFFF) { // Four bytes
+            if (di + 3 >= destLimit) break;
+            dest[di++] = 0xF0 + (codePoint >> 18);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = 0x80 + (codePoint & 0x3F);
+        } else if (codePoint <= 0x3FFFFFF) { // Five bytes
+            if (di + 4 >= destLimit) break;
+            dest[di++] = 0xF8 + (codePoint >> 24);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else if (codePoint <= 0x7FFFFFFF) { // Six bytes
+            if (di + 5 >= destLimit) break;
+            dest[di++] = 0xFC + (codePoint >> 30);
+            dest[di++] = 0x80 + ((codePoint >> 24) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 18) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 12) & 0x3F);
+            dest[di++] = 0x80 + ((codePoint >> 6) & 0x3F);
+            dest[di++] = codePoint & 0x3F;
+        } else {
+            // Not a code point... skip.
+        }
+    }
+    dest[di] = 0;
+    return di;
+}
 
 #if defined(FLAG_DO_PROFILE) || defined(FLAG_DBG)
 #include <android/log.h>
@@ -46,35 +96,13 @@
 
 #define DUMP_RESULT(words, frequencies) do { dumpResult(words, frequencies); } while (0)
 #define DUMP_WORD(word, length) do { dumpWord(word, length); } while (0)
-#define INTS_TO_CHARS(input, length, output) do { \
-        intArrayToCharArray(input, length, output); } while (0)
-
-// TODO: Support full UTF-8 conversion
-AK_FORCE_INLINE static int intArrayToCharArray(const int *source, const int sourceSize,
-        char *dest) {
-    int si = 0;
-    int di = 0;
-    while (si < sourceSize && di < MAX_WORD_LENGTH - 1 && 0 != source[si]) {
-        const int codePoint = source[si++];
-        if (codePoint < 0x7F) {
-            dest[di++] = codePoint;
-        } else if (codePoint < 0x7FF) {
-            dest[di++] = 0xC0 + (codePoint >> 6);
-            dest[di++] = 0x80 + (codePoint & 0x3F);
-        } else if (codePoint < 0xFFFF) {
-            dest[di++] = 0xE0 + (codePoint >> 12);
-            dest[di++] = 0x80 + ((codePoint & 0xFC0) >> 6);
-            dest[di++] = 0x80 + (codePoint & 0x3F);
-        }
-    }
-    dest[di] = 0;
-    return di;
-}
+#define INTS_TO_CHARS(input, length, output, outlength) do { \
+        intArrayToCharArray(input, length, output, outlength); } while (0)
 
 static inline void dumpWordInfo(const int *word, const int length, const int rank,
         const int probability) {
     static char charBuf[50];
-    const int N = intArrayToCharArray(word, length, charBuf);
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
     if (N > 1) {
         AKLOGI("%2d [ %s ] (%d)", rank, charBuf, probability);
     }
@@ -90,7 +118,7 @@
 
 static AK_FORCE_INLINE void dumpWord(const int *word, const int length) {
     static char charBuf[50];
-    const int N = intArrayToCharArray(word, length, charBuf);
+    const int N = intArrayToCharArray(word, length, charBuf, NELEMS(charBuf));
     if (N > 1) {
         AKLOGI("[ %s ]", charBuf);
     }
@@ -304,8 +332,6 @@
 template<typename T> AK_FORCE_INLINE const T &min(const T &a, const T &b) { return a < b ? a : b; }
 template<typename T> AK_FORCE_INLINE const T &max(const T &a, const T &b) { return a > b ? a : b; }
 
-#define NELEMS(x) (sizeof(x) / sizeof((x)[0]))
-
 // DEBUG
 #define INPUTLENGTH_FOR_DEBUG (-1)
 #define MIN_OUTPUT_INDEX_FOR_DEBUG (-1)
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 52db8e9..973da67 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -28,15 +28,16 @@
 #if DEBUG_DICT
 #define LOGI_SHOW_ADD_COST_PROP \
         do { char charBuf[50]; \
-        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf); \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
         AKLOGI("%20s, \"%c\", size = %03d, total = %03d, index(0) = %02d, dist = %.4f, %s,,", \
                 __FUNCTION__, getNodeCodePoint(), inputSize, getTotalInputIndex(), \
                 getInputIndex(0), getNormalizedCompoundDistance(), charBuf); } while (0)
 #define DUMP_WORD_AND_SCORE(header) \
         do { char charBuf[50]; char prevWordCharBuf[50]; \
-        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf); \
+        INTS_TO_CHARS(getOutputWordBuf(), getNodeCodePointCount(), charBuf, NELEMS(charBuf)); \
         INTS_TO_CHARS(mDicNodeState.mDicNodeStatePrevWord.mPrevWord, \
-                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf); \
+                mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength(), prevWordCharBuf, \
+                NELEMS(prevWordCharBuf)); \
         AKLOGI("#%8s, %5f, %5f, %5f, %5f, %s, %s, %d,,", header, \
                 getSpatialDistanceForScoring(), getLanguageDistanceForScoring(), \
                 getNormalizedCompoundDistance(), getRawLength(), prevWordCharBuf, charBuf, \
@@ -111,32 +112,23 @@
         mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS, 0 /* flags */, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
+                NOT_A_DICT_POS, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
                 NOT_A_CODE_POINT /* nodeCodePoint */, NOT_A_PROBABILITY /* probability */,
-                false /* isTerminal */, true /* hasChildren */, 0 /* depth */,
-                0 /* terminalDepth */);
+                false /* isTerminal */, true /* hasChildren */,
+                false /* isBlacklistedOrNotAWord */, 0 /* depth */, 0 /* terminalDepth */);
         mDicNodeState.init(prevWordNodePos);
         PROF_NODE_RESET(mProfiler);
     }
 
-    void initAsPassingChild(DicNode *parentNode) {
-        mIsUsed = true;
-        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
-        const int c = parentNode->getNodeTypedCodePoint();
-        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
-        mDicNodeState.init(&parentNode->mDicNodeState);
-        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
-    }
-
     // Init for root with previous word
     void initAsRootWithPreviousWord(DicNode *dicNode, const int rootGroupPos) {
         mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(
-                NOT_A_DICT_POS,  0 /* flags */, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
+                NOT_A_DICT_POS, rootGroupPos, NOT_A_DICT_POS /* attributesPos */,
                 NOT_A_CODE_POINT /* nodeCodePoint */, NOT_A_PROBABILITY /* probability */,
-                false /* isTerminal */, true /* hasChildren */, 0 /* depth */,
-                0 /* terminalDepth */);
+                false /* isTerminal */, true /* hasChildren */,
+                false /* isBlacklistedOrNotAWord */,  0 /* depth */, 0 /* terminalDepth */);
         // TODO: Move to dicNodeState?
         mDicNodeState.mDicNodeStateOutput.init(); // reset for next word
         mDicNodeState.mDicNodeStateInput.init(
@@ -156,18 +148,27 @@
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    // TODO: minimize arguments by looking binary_format
-    void initAsChild(DicNode *dicNode, const int pos, const uint8_t flags, const int childrenPos,
+    void initAsPassingChild(DicNode *parentNode) {
+        mIsUsed = true;
+        mIsCachedForNextSuggestion = parentNode->mIsCachedForNextSuggestion;
+        const int c = parentNode->getNodeTypedCodePoint();
+        mDicNodeProperties.init(&parentNode->mDicNodeProperties, c);
+        mDicNodeState.init(&parentNode->mDicNodeState);
+        PROF_NODE_COPY(&parentNode->mProfiler, mProfiler);
+    }
+
+    void initAsChild(DicNode *dicNode, const int pos, const int childrenPos,
             const int attributesPos, const int probability, const bool isTerminal,
-            const bool hasChildren, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         mIsUsed = true;
         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
                 dicNode->mDicNodeProperties.getLeavingDepth() + mergedNodeCodePointCount);
-        mDicNodeProperties.init(pos, flags, childrenPos, attributesPos, mergedNodeCodePoints[0],
-                probability, isTerminal, hasChildren, newDepth, newLeavingDepth);
+        mDicNodeProperties.init(pos, childrenPos, attributesPos, mergedNodeCodePoints[0],
+                probability, isTerminal, hasChildren, isBlacklistedOrNotAWord, newDepth,
+                newLeavingDepth);
         mDicNodeState.init(&dicNode->mDicNodeState, mergedNodeCodePointCount,
                 mergedNodeCodePoints);
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
@@ -215,7 +216,7 @@
     }
 
     bool isImpossibleBigramWord() const {
-        if (mDicNodeProperties.hasBlacklistedOrNotAWordFlag()) {
+        if (isBlacklistedOrNotAWord()) {
             return true;
         }
         const int prevWordLen = mDicNodeState.mDicNodeStatePrevWord.getPrevWordLength()
@@ -462,8 +463,8 @@
         return mDicNodeState.mDicNodeStateScoring.isExactMatch();
     }
 
-    uint8_t getFlags() const {
-        return mDicNodeProperties.getFlags();
+    bool isBlacklistedOrNotAWord() const {
+        return mDicNodeProperties.isBlacklistedOrNotAWord();
     }
 
     int getAttributesPos() const {
@@ -503,6 +504,12 @@
         if (!right->isUsed()) {
             return false;
         }
+        // Promote exact matches to prevent them from being pruned.
+        const bool leftExactMatch = isExactMatch();
+        const bool rightExactMatch = right->isExactMatch();
+        if (leftExactMatch != rightExactMatch) {
+            return leftExactMatch;
+        }
         const float diff =
                 right->getNormalizedCompoundDistance() - getNormalizedCompoundDistance();
         static const float MIN_DIFF = 0.000001f;
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_properties.h b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
index 7e8aa49..d98000d 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_properties.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_properties.h
@@ -20,7 +20,6 @@
 #include <stdint.h>
 
 #include "defines.h"
-#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
@@ -32,24 +31,25 @@
 class DicNodeProperties {
  public:
     AK_FORCE_INLINE DicNodeProperties()
-            : mPos(0), mFlags(0), mChildrenPos(0), mAttributesPos(0), mProbability(0),
-              mNodeCodePoint(0), mDepth(0), mLeavingDepth(0), mIsTerminal(false),
-              mHasChildren(false) {}
+            : mPos(0), mChildrenPos(0), mAttributesPos(0), mProbability(0),
+              mNodeCodePoint(0), mIsTerminal(false), mHasChildren(false),
+              mIsBlacklistedOrNotAWord(false), mDepth(0), mLeavingDepth(0) {}
 
     virtual ~DicNodeProperties() {}
 
     // Should be called only once per DicNode is initialized.
-    void init(const int pos, const uint8_t flags, const int childrenPos, const int attributesPos,
+    void init(const int pos, const int childrenPos, const int attributesPos,
             const int nodeCodePoint, const int probability, const bool isTerminal,
-            const bool hasChildren, const uint16_t depth, const uint16_t leavingDepth) {
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t depth, const uint16_t leavingDepth) {
         mPos = pos;
-        mFlags = flags;
         mChildrenPos = childrenPos;
         mAttributesPos = attributesPos;
         mNodeCodePoint = nodeCodePoint;
         mProbability = probability;
         mIsTerminal = isTerminal;
         mHasChildren = hasChildren;
+        mIsBlacklistedOrNotAWord = isBlacklistedOrNotAWord;
         mDepth = depth;
         mLeavingDepth = leavingDepth;
     }
@@ -57,13 +57,13 @@
     // Init for copy
     void init(const DicNodeProperties *const nodeProp) {
         mPos = nodeProp->mPos;
-        mFlags = nodeProp->mFlags;
         mChildrenPos = nodeProp->mChildrenPos;
         mAttributesPos = nodeProp->mAttributesPos;
         mNodeCodePoint = nodeProp->mNodeCodePoint;
         mProbability = nodeProp->mProbability;
         mIsTerminal = nodeProp->mIsTerminal;
         mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
         mDepth = nodeProp->mDepth;
         mLeavingDepth = nodeProp->mLeavingDepth;
     }
@@ -71,13 +71,13 @@
     // Init as passing child
     void init(const DicNodeProperties *const nodeProp, const int codePoint) {
         mPos = nodeProp->mPos;
-        mFlags = nodeProp->mFlags;
         mChildrenPos = nodeProp->mChildrenPos;
         mAttributesPos = nodeProp->mAttributesPos;
         mNodeCodePoint = codePoint; // Overwrite the node char of a passing child
         mProbability = nodeProp->mProbability;
         mIsTerminal = nodeProp->mIsTerminal;
         mHasChildren = nodeProp->mHasChildren;
+        mIsBlacklistedOrNotAWord = nodeProp->mIsBlacklistedOrNotAWord;
         mDepth = nodeProp->mDepth + 1; // Increment the depth of a passing child
         mLeavingDepth = nodeProp->mLeavingDepth;
     }
@@ -86,10 +86,6 @@
         return mPos;
     }
 
-    uint8_t getFlags() const {
-        return mFlags;
-    }
-
     int getChildrenPos() const {
         return mChildrenPos;
     }
@@ -123,8 +119,8 @@
         return mHasChildren || mDepth != mLeavingDepth;
     }
 
-    bool hasBlacklistedOrNotAWordFlag() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mFlags);
+    bool isBlacklistedOrNotAWord() const {
+        return mIsBlacklistedOrNotAWord;
     }
 
  private:
@@ -132,15 +128,15 @@
     // Use a default copy constructor and an assign operator because shallow copies are ok
     // for this class
     int mPos;
-    uint8_t mFlags;
     int mChildrenPos;
     int mAttributesPos;
     int mProbability;
     int mNodeCodePoint;
-    uint16_t mDepth;
-    uint16_t mLeavingDepth;
     bool mIsTerminal;
     bool mHasChildren;
+    bool mIsBlacklistedOrNotAWord;
+    uint16_t mDepth;
+    uint16_t mLeavingDepth;
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PROPERTIES_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h b/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
new file mode 100644
index 0000000..1a39f2e
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_proximity_filter.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_PROXIMITY_FILTER_H
+#define LATINIME_DIC_NODE_PROXIMITY_FILTER_H
+
+#include "defines.h"
+#include "suggest/core/layout/proximity_info_state.h"
+#include "suggest/core/layout/proximity_info_utils.h"
+#include "suggest/core/policy/dictionary_structure_policy.h"
+
+namespace latinime {
+
+class DicNodeProximityFilter : public DictionaryStructurePolicy::NodeFilter {
+ public:
+    DicNodeProximityFilter(const ProximityInfoState *const pInfoState,
+            const int pointIndex, const bool exactOnly)
+            : mProximityInfoState(pInfoState), mPointIndex(pointIndex), mExactOnly(exactOnly) {}
+
+    bool isFilteredOut(const int codePoint) const {
+        return !isProximityCodePoint(codePoint);
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeProximityFilter);
+
+    const ProximityInfoState *const mProximityInfoState;
+    const int mPointIndex;
+    const bool mExactOnly;
+
+    // TODO: Move to proximity info state
+    bool isProximityCodePoint(const int codePoint) const {
+        if (!mProximityInfoState) {
+            return true;
+        }
+        if (mExactOnly) {
+            return mProximityInfoState->getPrimaryCodePointAt(mPointIndex) == codePoint;
+        }
+        const ProximityType matchedId = mProximityInfoState->getProximityType(
+                mPointIndex, codePoint, true /* checkProximityChars */);
+        return ProximityInfoUtils::isMatchOrProximityChar(matchedId);
+    }
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_PROXIMITY_FILTER_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
index 9bf7ece..6c7f666 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.cpp
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
+#include "suggest/core/dicnode/dic_node_utils.h"
+
 #include <cstring>
-#include <vector>
 
 #include "suggest/core/dicnode/dic_node.h"
-#include "suggest/core/dicnode/dic_node_utils.h"
+#include "suggest/core/dicnode/dic_node_proximity_filter.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/binary_format.h"
 #include "suggest/core/dictionary/multi_bigram_map.h"
 #include "suggest/core/dictionary/probability_utils.h"
-#include "suggest/core/layout/proximity_info.h"
-#include "suggest/core/layout/proximity_info_state.h"
 #include "suggest/core/policy/dictionary_structure_policy.h"
 #include "utils/char_utils.h"
 
@@ -57,21 +56,20 @@
 ///////////////////////////////////
 
 /* static */ void DicNodeUtils::createAndGetPassingChildNode(DicNode *dicNode,
-        const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
+        const DicNodeProximityFilter *const childrenFilter,
         DicNodeVector *childDicNodes) {
     // Passing multiple chars node. No need to traverse child
     const int codePoint = dicNode->getNodeTypedCodePoint();
     const int baseLowerCaseCodePoint = CharUtils::toBaseLowerCase(codePoint);
-    const bool isMatch = isMatchedNodeCodePoint(pInfoState, pointIndex, exactOnly, codePoint);
-    if (isMatch || CharUtils::isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
+    if (!childrenFilter->isFilteredOut(codePoint)
+            || CharUtils::isIntentionalOmissionCodePoint(baseLowerCaseCodePoint)) {
         childDicNodes->pushPassingChild(dicNode);
     }
 }
 
 /* static */ int DicNodeUtils::createAndGetLeavingChildNode(DicNode *dicNode, int pos,
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
-        const std::vector<int> *const codePointsFilter, const ProximityInfo *const pInfo,
+        const DicNodeProximityFilter *const childrenFilter,
         DicNodeVector *childDicNodes) {
     int nextPos = pos;
     const uint8_t flags = BinaryFormat::getFlagsAndForwardPointer(
@@ -80,6 +78,7 @@
     const bool isTerminal = (0 != (BinaryFormat::FLAG_IS_TERMINAL & flags));
     const bool hasChildren = BinaryFormat::hasChildrenInFlags(flags);
     const bool hasShortcuts = (0 != (BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS & flags));
+    const bool isBlacklistedOrNotAWord = BinaryFormat::hasBlacklistedOrNotAWordFlag(flags);
 
     int codePoint = BinaryFormat::getCodePointAndForwardPointer(
             binaryDictionaryInfo->getDictRoot(), &pos);
@@ -110,50 +109,18 @@
     const int siblingPos = BinaryFormat::skipChildrenPosAndAttributes(
             binaryDictionaryInfo->getDictRoot(), flags, pos);
 
-    if (isDicNodeFilteredOut(mergedNodeCodePoints[0], pInfo, codePointsFilter)) {
+    if (childrenFilter->isFilteredOut(mergedNodeCodePoints[0])) {
         return siblingPos;
     }
-    if (!isMatchedNodeCodePoint(pInfoState, pointIndex, exactOnly, mergedNodeCodePoints[0])) {
-        return siblingPos;
-    }
-    childDicNodes->pushLeavingChild(dicNode, nextPos, flags, childrenPos, attributesPos,
-            probability, isTerminal, hasChildren, mergedNodeCodePointCount, mergedNodeCodePoints);
+    childDicNodes->pushLeavingChild(dicNode, nextPos, childrenPos, attributesPos,
+            probability, isTerminal, hasChildren, isBlacklistedOrNotAWord,
+            mergedNodeCodePointCount, mergedNodeCodePoints);
     return siblingPos;
 }
 
-/* static */ bool DicNodeUtils::isDicNodeFilteredOut(const int nodeCodePoint,
-        const ProximityInfo *const pInfo, const std::vector<int> *const codePointsFilter) {
-    const int filterSize = codePointsFilter ? codePointsFilter->size() : 0;
-    if (filterSize <= 0) {
-        return false;
-    }
-    if (pInfo && (pInfo->getKeyIndexOf(nodeCodePoint) == NOT_AN_INDEX
-            || CharUtils::isIntentionalOmissionCodePoint(nodeCodePoint))) {
-        // If normalized nodeCodePoint is not on the keyboard or skippable, this child is never
-        // filtered.
-        return false;
-    }
-    const int lowerCodePoint = CharUtils::toLowerCase(nodeCodePoint);
-    const int baseLowerCodePoint = CharUtils::toBaseCodePoint(lowerCodePoint);
-    // TODO: Avoid linear search
-    for (int i = 0; i < filterSize; ++i) {
-        // Checking if a normalized code point is in filter characters when pInfo is not
-        // null. When pInfo is null, nodeCodePoint is used to check filtering without
-        // normalizing.
-        if ((pInfo && ((*codePointsFilter)[i] == lowerCodePoint
-                || (*codePointsFilter)[i] == baseLowerCodePoint))
-                        || (!pInfo && (*codePointsFilter)[i] == nodeCodePoint)) {
-            return false;
-        }
-    }
-    return true;
-}
-
 /* static */ void DicNodeUtils::createAndGetAllLeavingChildNodes(DicNode *dicNode,
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
-        const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
-        const std::vector<int> *const codePointsFilter, const ProximityInfo *const pInfo,
-        DicNodeVector *childDicNodes) {
+        const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes) {
     if (!dicNode->hasChildren()) {
         return;
     }
@@ -161,14 +128,8 @@
     const int childCount = BinaryFormat::getGroupCountAndForwardPointer(
             binaryDictionaryInfo->getDictRoot(), &nextPos);
     for (int i = 0; i < childCount; i++) {
-        const int filterSize = codePointsFilter ? codePointsFilter->size() : 0;
         nextPos = createAndGetLeavingChildNode(dicNode, nextPos, binaryDictionaryInfo,
-                pInfoState, pointIndex, exactOnly, codePointsFilter, pInfo,
-                childDicNodes);
-        if (!pInfo && filterSize > 0 && childDicNodes->exceeds(filterSize)) {
-            // All code points have been found.
-            break;
-        }
+                childrenFilter, childDicNodes);
     }
 }
 
@@ -184,13 +145,12 @@
     if (dicNode->isTotalInputSizeExceedingLimit()) {
         return;
     }
+    const DicNodeProximityFilter childrenFilter(pInfoState, pointIndex, exactOnly);
     if (!dicNode->isLeavingNode()) {
-        DicNodeUtils::createAndGetPassingChildNode(dicNode, pInfoState, pointIndex, exactOnly,
-                childDicNodes);
+        DicNodeUtils::createAndGetPassingChildNode(dicNode, &childrenFilter, childDicNodes);
     } else {
         DicNodeUtils::createAndGetAllLeavingChildNodes(
-                dicNode, binaryDictionaryInfo, pInfoState, pointIndex, exactOnly,
-                0 /* codePointsFilter */, 0 /* pInfo */, childDicNodes);
+                dicNode, binaryDictionaryInfo, &childrenFilter, childDicNodes);
     }
 }
 
@@ -230,23 +190,6 @@
     return ProbabilityUtils::backoff(unigramProbability);
 }
 
-///////////////////////////////////////
-// Bigram / Unigram dictionary utils //
-///////////////////////////////////////
-
-/* static */ bool DicNodeUtils::isMatchedNodeCodePoint(const ProximityInfoState *pInfoState,
-        const int pointIndex, const bool exactOnly, const int nodeCodePoint) {
-    if (!pInfoState) {
-        return true;
-    }
-    if (exactOnly) {
-        return pInfoState->getPrimaryCodePointAt(pointIndex) == nodeCodePoint;
-    }
-    const ProximityType matchedId = pInfoState->getProximityType(pointIndex, nodeCodePoint,
-            true /* checkProximityChars */);
-    return isProximityChar(matchedId);
-}
-
 ////////////////
 // Char utils //
 ////////////////
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_utils.h b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
index d526975..7b567b5 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_utils.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_utils.h
@@ -18,7 +18,6 @@
 #define LATINIME_DIC_NODE_UTILS_H
 
 #include <stdint.h>
-#include <vector>
 
 #include "defines.h"
 
@@ -26,8 +25,8 @@
 
 class BinaryDictionaryInfo;
 class DicNode;
+class DicNodeProximityFilter;
 class DicNodeVector;
-class ProximityInfo;
 class ProximityInfoState;
 class MultiBigramMap;
 
@@ -44,19 +43,12 @@
             const BinaryDictionaryInfo *const binaryDictionaryInfo, DicNodeVector *childDicNodes);
     static float getBigramNodeImprobability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const DicNode *const node, MultiBigramMap *const multiBigramMap);
-    static bool isDicNodeFilteredOut(const int nodeCodePoint, const ProximityInfo *const pInfo,
-            const std::vector<int> *const codePointsFilter);
     // TODO: Move to private
     static void getProximityChildDicNodes(DicNode *dicNode,
             const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const ProximityInfoState *pInfoState, const int pointIndex, bool exactOnly,
             DicNodeVector *childDicNodes);
 
-    // TODO: Move to proximity info
-    static bool isProximityChar(ProximityType type) {
-        return type == MATCH_CHAR || type == PROXIMITY_CHAR || type == ADDITIONAL_PROXIMITY_CHAR;
-    }
-
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodeUtils);
     // Max number of bigrams to look up
@@ -64,22 +56,14 @@
 
     static int getBigramNodeProbability(const BinaryDictionaryInfo *const binaryDictionaryInfo,
             const DicNode *const node, MultiBigramMap *multiBigramMap);
-    static void createAndGetPassingChildNode(DicNode *dicNode, const ProximityInfoState *pInfoState,
-            const int pointIndex, const bool exactOnly, DicNodeVector *childDicNodes);
+    static void createAndGetPassingChildNode(DicNode *dicNode,
+            const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes);
     static void createAndGetAllLeavingChildNodes(DicNode *dicNode,
             const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const ProximityInfoState *pInfoState, const int pointIndex, const bool exactOnly,
-            const std::vector<int> *const codePointsFilter,
-            const ProximityInfo *const pInfo, DicNodeVector *childDicNodes);
+            const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes);
     static int createAndGetLeavingChildNode(DicNode *dicNode, int pos,
             const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const ProximityInfoState *pInfoState, const int pointIndex,
-            const bool exactOnly, const std::vector<int> *const codePointsFilter,
-            const ProximityInfo *const pInfo, DicNodeVector *childDicNodes);
-
-    // TODO: Move to proximity info
-    static bool isMatchedNodeCodePoint(const ProximityInfoState *pInfoState, const int pointIndex,
-            const bool exactOnly, const int nodeCodePoint);
+            const DicNodeProximityFilter *const childrenFilter, DicNodeVector *childDicNodes);
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_UTILS_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_vector.h b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
index 9641cc1..5ac4eea 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_vector.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_vector.h
@@ -62,14 +62,15 @@
         mDicNodes.back().initAsPassingChild(dicNode);
     }
 
-    void pushLeavingChild(DicNode *dicNode, const int pos, const uint8_t flags,
-            const int childrenPos, const int attributesPos, const int probability,
-            const bool isTerminal, const bool hasChildren, const uint16_t mergedNodeCodePointCount,
-            const int *const mergedNodeCodePoints) {
+    void pushLeavingChild(DicNode *dicNode, const int pos, const int childrenPos,
+            const int attributesPos, const int probability, const bool isTerminal,
+            const bool hasChildren, const bool isBlacklistedOrNotAWord,
+            const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
         ASSERT(!mLock);
         mDicNodes.push_back(mEmptyNode);
-        mDicNodes.back().initAsChild(dicNode, pos, flags, childrenPos, attributesPos, probability,
-                isTerminal, hasChildren, mergedNodeCodePointCount, mergedNodeCodePoints);
+        mDicNodes.back().initAsChild(dicNode, pos, childrenPos, attributesPos, probability,
+                isTerminal, hasChildren, isBlacklistedOrNotAWord, mergedNodeCodePointCount,
+                mergedNodeCodePoints);
     }
 
     DicNode *operator[](const int id) {
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
index bbb4ca3..5d14a05 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.cpp
@@ -27,17 +27,13 @@
 /**
  * Format versions
  */
-// Originally, format version 1 had a 16-bit magic number, then the version number `01'
-// then options that must be 0. Hence the first 32-bits of the format are always as follow
-// and it's okay to consider them a magic number as a whole.
-const uint32_t BinaryDictionaryFormatUtils::FORMAT_VERSION_1_MAGIC_NUMBER = 0x78B10100;
 
 // The versions of Latin IME that only handle format version 1 only test for the magic
 // number, so we had to change it so that version 2 files would be rejected by older
 // implementations. On this occasion, we made the magic number 32 bits long.
-const uint32_t BinaryDictionaryFormatUtils::FORMAT_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
+const uint32_t BinaryDictionaryFormatUtils::HEADER_VERSION_2_MAGIC_NUMBER = 0x9BC13AFE;
 // Magic number (4 bytes), version (2 bytes), options (2 bytes), header size (4 bytes) = 12
-const int BinaryDictionaryFormatUtils::FORMAT_VERSION_2_MINIMUM_SIZE = 12;
+const int BinaryDictionaryFormatUtils::HEADER_VERSION_2_MINIMUM_SIZE = 12;
 
 /* static */ BinaryDictionaryFormatUtils::FORMAT_VERSION
         BinaryDictionaryFormatUtils::detectFormatVersion(const uint8_t *const dict,
@@ -50,31 +46,28 @@
     }
     const uint32_t magicNumber = ByteArrayUtils::readUint32(dict, 0);
     switch (magicNumber) {
-    case FORMAT_VERSION_1_MAGIC_NUMBER:
-        // Format 1 header is exactly 5 bytes long and looks like:
-        // Magic number (2 bytes) 0x78 0xB1
-        // Version number (1 byte) 0x01
-        // Options (2 bytes) must be 0x00 0x00
-        return VERSION_1;
-    case FORMAT_VERSION_2_MAGIC_NUMBER:
-        // Version 2 dictionaries are at least 12 bytes long.
-        // If this dictionary has the version 2 magic number but is less than 12 bytes long,
-        // then it's an unknown format and we need to avoid confidently reading the next bytes.
-        if (dictSize < FORMAT_VERSION_2_MINIMUM_SIZE) {
+        case HEADER_VERSION_2_MAGIC_NUMBER:
+            // Version 2 header are at least 12 bytes long.
+            // If this header has the version 2 magic number but is less than 12 bytes long,
+            // then it's an unknown format and we need to avoid confidently reading the next bytes.
+            if (dictSize < HEADER_VERSION_2_MINIMUM_SIZE) {
+                return UNKNOWN_VERSION;
+            }
+            // Version 2 header is as follows:
+            // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
+            // Version number (2 bytes)
+            // Options (2 bytes)
+            // Header size (4 bytes) : integer, big endian
+            if (ByteArrayUtils::readUint16(dict, 4) == 2) {
+                return VERSION_2;
+            } else if (ByteArrayUtils::readUint16(dict, 4) == 3) {
+                // TODO: Support version 3 dictionary.
+                return UNKNOWN_VERSION;
+            } else {
+                return UNKNOWN_VERSION;
+            }
+        default:
             return UNKNOWN_VERSION;
-        }
-        // Format 2 header is as follows:
-        // Magic number (4 bytes) 0x9B 0xC1 0x3A 0xFE
-        // Version number (2 bytes) 0x00 0x02
-        // Options (2 bytes)
-        // Header size (4 bytes) : integer, big endian
-        if (ByteArrayUtils::readUint16(dict, 4) == 2) {
-            return VERSION_2;
-        } else {
-            return UNKNOWN_VERSION;
-        }
-    default:
-        return UNKNOWN_VERSION;
     }
 }
 
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
index 33618b9..830684c 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_format_utils.h
@@ -33,10 +33,9 @@
  */
 class BinaryDictionaryFormatUtils {
  public:
-    // TODO: Remove obsolete version logic
     enum FORMAT_VERSION {
-        VERSION_1,
         VERSION_2,
+        VERSION_3,
         UNKNOWN_VERSION
     };
 
@@ -46,9 +45,8 @@
     DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryFormatUtils);
 
     static const int DICTIONARY_MINIMUM_SIZE;
-    static const uint32_t FORMAT_VERSION_1_MAGIC_NUMBER;
-    static const uint32_t FORMAT_VERSION_2_MAGIC_NUMBER;
-    static const int FORMAT_VERSION_2_MINIMUM_SIZE;
+    static const uint32_t HEADER_VERSION_2_MAGIC_NUMBER;
+    static const int HEADER_VERSION_2_MINIMUM_SIZE;
 };
 } // namespace latinime
 #endif /* LATINIME_BINARY_DICTIONARY_FORMAT_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
index 6dba0b2..240512b 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header.h
@@ -53,6 +53,20 @@
         return mMultiWordCostMultiplier;
     }
 
+    AK_FORCE_INLINE void readHeaderValueOrQuestionMark(const char *const key,
+            int *outValue, int outValueSize) const {
+        if (outValueSize <= 0) return;
+        if (outValueSize == 1) {
+            outValue[0] = '\0';
+            return;
+        }
+        if (!BinaryDictionaryHeaderReadingUtils::readHeaderValue(mBinaryDictionaryInfo,
+                key, outValue, outValueSize)) {
+            outValue[0] = '?';
+            outValue[1] = '\0';
+        }
+    }
+
  private:
     DISALLOW_COPY_AND_ASSIGN(BinaryDictionaryHeader);
 
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
index 2c95931..a57b0f8 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.cpp
@@ -26,12 +26,10 @@
 
 const int BinaryDictionaryHeaderReadingUtils::MAX_OPTION_KEY_LENGTH = 256;
 
-const int BinaryDictionaryHeaderReadingUtils::FORMAT_VERSION_1_HEADER_SIZE = 5;
-
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_MAGIC_NUMBER_SIZE = 4;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_DICTIONARY_VERSION_SIZE = 2;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_DICTIONARY_FLAG_SIZE = 2;
-const int BinaryDictionaryHeaderReadingUtils::VERSION_2_DICTIONARY_HEADER_SIZE_SIZE = 4;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_MAGIC_NUMBER_SIZE = 4;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_DICTIONARY_VERSION_SIZE = 2;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_FLAG_SIZE = 2;
+const int BinaryDictionaryHeaderReadingUtils::VERSION_2_HEADER_SIZE_FIELD_SIZE = 4;
 
 const BinaryDictionaryHeaderReadingUtils::DictionaryFlags
         BinaryDictionaryHeaderReadingUtils::NO_FLAGS = 0;
@@ -47,15 +45,13 @@
 
 /* static */ int BinaryDictionaryHeaderReadingUtils::getHeaderSize(
         const BinaryDictionaryInfo *const binaryDictionaryInfo) {
-    switch (binaryDictionaryInfo->getFormat()) {
-        case BinaryDictionaryFormatUtils::VERSION_1:
-            return FORMAT_VERSION_1_HEADER_SIZE;
-        case BinaryDictionaryFormatUtils::VERSION_2:
+    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
+        case HEADER_VERSION_2:
             // See the format of the header in the comment in
             // BinaryDictionaryFormatUtils::detectFormatVersion()
             return ByteArrayUtils::readUint32(binaryDictionaryInfo->getDictBuf(),
-                    VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE
-                            + VERSION_2_DICTIONARY_FLAG_SIZE);
+                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
+                            + VERSION_2_HEADER_FLAG_SIZE);
         default:
             return S_INT_MAX;
     }
@@ -64,12 +60,10 @@
 /* static */ BinaryDictionaryHeaderReadingUtils::DictionaryFlags
         BinaryDictionaryHeaderReadingUtils::getFlags(
                 const BinaryDictionaryInfo *const binaryDictionaryInfo) {
-    switch (binaryDictionaryInfo->getFormat()) {
-        case BinaryDictionaryFormatUtils::VERSION_1:
-            return NO_FLAGS;
-        case BinaryDictionaryFormatUtils::VERSION_2:
+    switch (getHeaderVersion(binaryDictionaryInfo->getFormat())) {
+        case HEADER_VERSION_2:
             return ByteArrayUtils::readUint16(binaryDictionaryInfo->getDictBuf(),
-                    VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE);
+                    VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE);
         default:
             return NO_FLAGS;
     }
@@ -79,17 +73,23 @@
 /* static */ bool BinaryDictionaryHeaderReadingUtils::readHeaderValue(
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const char *const key, int *outValue, const int outValueSize) {
-    if (outValueSize <= 0 || !hasHeaderAttributes(binaryDictionaryInfo->getFormat())) {
+    if (outValueSize <= 0) {
         return false;
     }
     const int headerSize = getHeaderSize(binaryDictionaryInfo);
     int pos = getHeaderOptionsPosition(binaryDictionaryInfo->getFormat());
+    if (pos == NOT_A_DICT_POS) {
+        // The header doesn't have header options.
+        return false;
+    }
     while (pos < headerSize) {
         if(ByteArrayUtils::compareStringInBufferWithCharArray(
                 binaryDictionaryInfo->getDictBuf(), key, headerSize - pos, &pos) == 0) {
             // The key was found.
-            ByteArrayUtils::readStringAndAdvancePosition(
+            const int length = ByteArrayUtils::readStringAndAdvancePosition(
                     binaryDictionaryInfo->getDictBuf(), outValueSize, outValue, &pos);
+            // Add a 0 terminator to the string.
+            outValue[length < outValueSize ? length : outValueSize - 1] = '\0';
             return true;
         }
         ByteArrayUtils::advancePositionToBehindString(
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
index 49ed2b9..6174822 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_header_reading_utils.h
@@ -48,27 +48,15 @@
         return (flags & FRENCH_LIGATURE_PROCESSING_FLAG) != 0;
     }
 
-    static AK_FORCE_INLINE bool hasHeaderAttributes(
-            const BinaryDictionaryFormatUtils::FORMAT_VERSION format) {
-        // Only format 2 and above have header attributes as {key,value} string pairs.
-        switch (format) {
-        case BinaryDictionaryFormatUtils::VERSION_2:
-            return  true;
-            break;
-        default:
-            return false;
-        }
-    }
-
     static AK_FORCE_INLINE int getHeaderOptionsPosition(
-            const BinaryDictionaryFormatUtils::FORMAT_VERSION format) {
-        switch (format) {
-        case BinaryDictionaryFormatUtils::VERSION_2:
-            return VERSION_2_MAGIC_NUMBER_SIZE + VERSION_2_DICTIONARY_VERSION_SIZE
-                    + VERSION_2_DICTIONARY_FLAG_SIZE + VERSION_2_DICTIONARY_HEADER_SIZE_SIZE;
+            const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
+        switch (getHeaderVersion(dictionaryFormat)) {
+        case HEADER_VERSION_2:
+            return VERSION_2_HEADER_MAGIC_NUMBER_SIZE + VERSION_2_HEADER_DICTIONARY_VERSION_SIZE
+                    + VERSION_2_HEADER_FLAG_SIZE + VERSION_2_HEADER_SIZE_FIELD_SIZE;
             break;
         default:
-            return 0;
+            return NOT_A_DICT_POS;
         }
     }
 
@@ -82,12 +70,15 @@
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(BinaryDictionaryHeaderReadingUtils);
 
-    static const int FORMAT_VERSION_1_HEADER_SIZE;
+    enum HEADER_VERSION {
+        HEADER_VERSION_2,
+        UNKNOWN_HEADER_VERSION
+    };
 
-    static const int VERSION_2_MAGIC_NUMBER_SIZE;
-    static const int VERSION_2_DICTIONARY_VERSION_SIZE;
-    static const int VERSION_2_DICTIONARY_FLAG_SIZE;
-    static const int VERSION_2_DICTIONARY_HEADER_SIZE_SIZE;
+    static const int VERSION_2_HEADER_MAGIC_NUMBER_SIZE;
+    static const int VERSION_2_HEADER_DICTIONARY_VERSION_SIZE;
+    static const int VERSION_2_HEADER_FLAG_SIZE;
+    static const int VERSION_2_HEADER_SIZE_FIELD_SIZE;
 
     static const DictionaryFlags NO_FLAGS;
     // Flags for special processing
@@ -97,6 +88,18 @@
     static const DictionaryFlags SUPPORTS_DYNAMIC_UPDATE_FLAG;
     static const DictionaryFlags FRENCH_LIGATURE_PROCESSING_FLAG;
     static const DictionaryFlags CONTAINS_BIGRAMS_FLAG;
+
+    static HEADER_VERSION getHeaderVersion(
+            const BinaryDictionaryFormatUtils::FORMAT_VERSION formatVersion) {
+        switch(formatVersion) {
+            case BinaryDictionaryFormatUtils::VERSION_2:
+                // Fall through
+            case BinaryDictionaryFormatUtils::VERSION_3:
+                return HEADER_VERSION_2;
+            default:
+                return UNKNOWN_HEADER_VERSION;
+        }
+    }
 };
 }
 #endif /* LATINIME_DICTIONARY_HEADER_READING_UTILS_H */
diff --git a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
index 7cb3144..cbea18f 100644
--- a/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
+++ b/native/jni/src/suggest/core/dictionary/binary_dictionary_info.h
@@ -20,23 +20,27 @@
 #include <stdint.h>
 
 #include "defines.h"
+#include "jni.h"
 #include "suggest/core/dictionary/binary_dictionary_format_utils.h"
 #include "suggest/core/dictionary/binary_dictionary_header.h"
 #include "suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h"
+#include "utils/log_utils.h"
 
 namespace latinime {
 
 class BinaryDictionaryInfo {
  public:
-    BinaryDictionaryInfo(const uint8_t *const dictBuf, const int dictSize, const int mmapFd,
-            const int dictBufOffset, const bool isUpdatable)
+     AK_FORCE_INLINE BinaryDictionaryInfo(JNIEnv *env, const uint8_t *const dictBuf,
+            const int dictSize, const int mmapFd, const int dictBufOffset, const bool isUpdatable)
             : mDictBuf(dictBuf), mDictSize(dictSize), mMmapFd(mmapFd),
               mDictBufOffset(dictBufOffset), mIsUpdatable(isUpdatable),
               mDictionaryFormat(BinaryDictionaryFormatUtils::detectFormatVersion(
                       mDictBuf, mDictSize)),
               mDictionaryHeader(this), mDictRoot(mDictBuf + mDictionaryHeader.getSize()),
               mStructurePolicy(DictionaryStructurePolicyFactory::getDictionaryStructurePolicy(
-                      mDictionaryFormat)) {}
+                      mDictionaryFormat)) {
+        logDictionaryInfo(env);
+    }
 
     AK_FORCE_INLINE const uint8_t *getDictBuf() const {
         return mDictBuf;
@@ -88,6 +92,33 @@
     const BinaryDictionaryHeader mDictionaryHeader;
     const uint8_t *const mDictRoot;
     const DictionaryStructurePolicy *const mStructurePolicy;
+
+    AK_FORCE_INLINE void logDictionaryInfo(JNIEnv *const env) const {
+        const int BUFFER_SIZE = 16;
+        int dictionaryIdCodePointBuffer[BUFFER_SIZE];
+        int versionStringCodePointBuffer[BUFFER_SIZE];
+        int dateStringCodePointBuffer[BUFFER_SIZE];
+        mDictionaryHeader.readHeaderValueOrQuestionMark("dictionary",
+                dictionaryIdCodePointBuffer, BUFFER_SIZE);
+        mDictionaryHeader.readHeaderValueOrQuestionMark("version",
+                versionStringCodePointBuffer, BUFFER_SIZE);
+        mDictionaryHeader.readHeaderValueOrQuestionMark("date",
+                dateStringCodePointBuffer, BUFFER_SIZE);
+
+        char dictionaryIdCharBuffer[BUFFER_SIZE];
+        char versionStringCharBuffer[BUFFER_SIZE];
+        char dateStringCharBuffer[BUFFER_SIZE];
+        intArrayToCharArray(dictionaryIdCodePointBuffer, BUFFER_SIZE,
+                dictionaryIdCharBuffer, BUFFER_SIZE);
+        intArrayToCharArray(versionStringCodePointBuffer, BUFFER_SIZE,
+                versionStringCharBuffer, BUFFER_SIZE);
+        intArrayToCharArray(dateStringCodePointBuffer, BUFFER_SIZE,
+                dateStringCharBuffer, BUFFER_SIZE);
+
+        LogUtils::logToJava(env,
+                "Dictionary info: dictionary = %s ; version = %s ; date = %s ; filesize = %i",
+                dictionaryIdCharBuffer, versionStringCharBuffer, dateStringCharBuffer, mDictSize);
+    }
 };
 }
 #endif /* LATINIME_BINARY_DICTIONARY_INFO_H */
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.cpp b/native/jni/src/suggest/core/dictionary/dictionary.cpp
index 675b549..f520a75 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.cpp
+++ b/native/jni/src/suggest/core/dictionary/dictionary.cpp
@@ -22,6 +22,7 @@
 #include <stdint.h>
 
 #include "defines.h"
+#include "jni.h"
 #include "suggest/core/dictionary/bigram_dictionary.h"
 #include "suggest/core/dictionary/binary_format.h"
 #include "suggest/core/session/dic_traverse_session.h"
@@ -32,8 +33,9 @@
 
 namespace latinime {
 
-Dictionary::Dictionary(void *dict, int dictSize, int mmapFd, int dictBufOffset, bool isUpdatable)
-        : mBinaryDictionaryInfo(static_cast<const uint8_t *>(dict), dictSize, mmapFd,
+Dictionary::Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd,
+        int dictBufOffset, bool isUpdatable)
+        : mBinaryDictionaryInfo(env, static_cast<const uint8_t *>(dict), dictSize, mmapFd,
                 dictBufOffset, isUpdatable),
           mBigramDictionary(new BigramDictionary(&mBinaryDictionaryInfo)),
           mGestureSuggest(new Suggest(GestureSuggestPolicyFactory::getGestureSuggestPolicy())),
diff --git a/native/jni/src/suggest/core/dictionary/dictionary.h b/native/jni/src/suggest/core/dictionary/dictionary.h
index 94579c2..1bf24a8 100644
--- a/native/jni/src/suggest/core/dictionary/dictionary.h
+++ b/native/jni/src/suggest/core/dictionary/dictionary.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 #include "defines.h"
+#include "jni.h"
 #include "suggest/core/dictionary/binary_dictionary_info.h"
 
 namespace latinime {
@@ -52,7 +53,8 @@
     static const int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000;
     static const int KIND_FLAG_EXACT_MATCH = 0x40000000;
 
-    Dictionary(void *dict, int dictSize, int mmapFd, int dictBufOffset, bool isUpdatable);
+    Dictionary(JNIEnv *env, void *dict, int dictSize, int mmapFd, int dictBufOffset,
+            bool isUpdatable);
 
     int getSuggestions(ProximityInfo *proximityInfo, DicTraverseSession *traverseSession,
             int *xcoordinates, int *ycoordinates, int *times, int *pointerIds, int *inputCodePoints,
diff --git a/native/jni/src/suggest/core/dictionary/terminal_attributes.h b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
index cec4708..0da6504 100644
--- a/native/jni/src/suggest/core/dictionary/terminal_attributes.h
+++ b/native/jni/src/suggest/core/dictionary/terminal_attributes.h
@@ -21,7 +21,6 @@
 
 #include "suggest/core/dictionary/binary_dictionary_info.h"
 #include "suggest/core/dictionary/binary_dictionary_terminal_attributes_reading_utils.h"
-#include "suggest/core/dictionary/binary_format.h"
 
 namespace latinime {
 
@@ -71,28 +70,23 @@
     };
 
     TerminalAttributes(const BinaryDictionaryInfo *const binaryDictionaryInfo,
-            const uint8_t nodeFlags, const int shortcutPos)
-            : mBinaryDictionaryInfo(binaryDictionaryInfo),
-              mNodeFlags(nodeFlags), mShortcutListSizePos(shortcutPos) {}
+            const int shortcutPos)
+            : mBinaryDictionaryInfo(binaryDictionaryInfo), mShortcutListSizePos(shortcutPos) {}
 
     inline ShortcutIterator getShortcutIterator() const {
-        // The size of the shortcuts is stored here so that the whole shortcut chunk can be
-        // skipped quickly, so we ignore it.
         int shortcutPos = mShortcutListSizePos;
-        BinaryDictionaryTerminalAttributesReadingUtils::getShortcutListSizeAndForwardPointer(
-                mBinaryDictionaryInfo, &shortcutPos);
-        const bool hasShortcutList = 0 != (mNodeFlags & BinaryFormat::FLAG_HAS_SHORTCUT_TARGETS);
+        const bool hasShortcutList = shortcutPos != NOT_A_DICT_POS;
+        if (hasShortcutList) {
+            BinaryDictionaryTerminalAttributesReadingUtils::getShortcutListSizeAndForwardPointer(
+                    mBinaryDictionaryInfo, &shortcutPos);
+        }
+        // shortcutPos is never used if hasShortcutList is false.
         return ShortcutIterator(mBinaryDictionaryInfo, shortcutPos, hasShortcutList);
     }
 
-    bool isBlacklistedOrNotAWord() const {
-        return BinaryFormat::hasBlacklistedOrNotAWordFlag(mNodeFlags);
-    }
-
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(TerminalAttributes);
     const BinaryDictionaryInfo *const mBinaryDictionaryInfo;
-    const uint8_t mNodeFlags;
     const int mShortcutListSizePos;
 };
 } // namespace latinime
diff --git a/native/jni/src/suggest/core/layout/proximity_info_utils.h b/native/jni/src/suggest/core/layout/proximity_info_utils.h
index 54f7539..0e28560 100644
--- a/native/jni/src/suggest/core/layout/proximity_info_utils.h
+++ b/native/jni/src/suggest/core/layout/proximity_info_utils.h
@@ -117,6 +117,10 @@
         return getSquaredDistanceFloat(x, y, projectionX, projectionY);
     }
 
+     static AK_FORCE_INLINE bool isMatchOrProximityChar(const ProximityType type) {
+         return type == MATCH_CHAR || type == PROXIMITY_CHAR || type == ADDITIONAL_PROXIMITY_CHAR;
+     }
+
     // Normal distribution N(u, sigma^2).
     struct NormalDistribution {
      public:
diff --git a/native/jni/src/suggest/core/policy/traversal.h b/native/jni/src/suggest/core/policy/traversal.h
index c6f66f2..f26d714 100644
--- a/native/jni/src/suggest/core/policy/traversal.h
+++ b/native/jni/src/suggest/core/policy/traversal.h
@@ -45,7 +45,7 @@
             const DicNode *const dicNode) const = 0;
     virtual bool needsToTraverseAllUserInput() const = 0;
     virtual float getMaxSpatialDistance() const = 0;
-    virtual bool allowPartialCommit() const = 0;
+    virtual bool autoCorrectsToMultiWordSuggestionIfTop() const = 0;
     virtual int getDefaultExpandDicNodeSize() const = 0;
     virtual int getMaxCacheSize() const = 0;
     virtual bool isPossibleOmissionChildNode(const DicTraverseSession *const traverseSession,
diff --git a/native/jni/src/suggest/core/suggest.cpp b/native/jni/src/suggest/core/suggest.cpp
index 173a612..c6da6f0 100644
--- a/native/jni/src/suggest/core/suggest.cpp
+++ b/native/jni/src/suggest/core/suggest.cpp
@@ -36,7 +36,6 @@
 const int Suggest::MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT = 16;
 const int Suggest::MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE = 2;
 const float Suggest::AUTOCORRECT_CLASSIFICATION_THRESHOLD = 0.33f;
-const int Suggest::FINAL_SCORE_PENALTY_FOR_NOT_BEST_EXACT_MATCHED_WORD = 1;
 
 /**
  * Returns a set of suggestions for the given input touch points. The commitPoint argument indicates
@@ -85,9 +84,9 @@
     if (!traverseSession->getProximityInfoState(0)->isUsed()) {
         return;
     }
-    if (TRAVERSAL->allowPartialCommit()) {
-        commitPoint = 0;
-    }
+
+    // Never auto partial commit for now.
+    commitPoint = 0;
 
     if (traverseSession->getInputSize() > MIN_CONTINUOUS_SUGGESTION_INPUT_SIZE
             && traverseSession->isContinuousSuggestionPossible()) {
@@ -149,8 +148,17 @@
             &doubleLetterTerminalIndex, &doubleLetterLevel);
 
     int maxScore = S_INT_MIN;
-    int bestExactMatchedNodeTerminalIndex = -1;
-    int bestExactMatchedNodeOutputWordIndex = -1;
+    // Force autocorrection for obvious long multi-word suggestions when the top suggestion is
+    // a long multiple words suggestion.
+    // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
+    // traverseSession->isPartiallyCommited() always returns false because we never auto partial
+    // commit for now.
+    const bool forceCommitMultiWords = (terminalSize > 0) ?
+            TRAVERSAL->autoCorrectsToMultiWordSuggestionIfTop()
+                    && (traverseSession->isPartiallyCommited()
+                            || (traverseSession->getInputSize()
+                                    >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
+                                            && terminals[0].hasMultipleWords())) : false;
     // Output suggestion results here
     for (int terminalIndex = 0; terminalIndex < terminalSize && outputWordIndex < MAX_RESULTS;
             ++terminalIndex) {
@@ -162,8 +170,6 @@
                 terminalIndex, doubleLetterTerminalIndex, doubleLetterLevel);
         const float compoundDistance = terminalDicNode->getCompoundDistance(languageWeight)
                 + doubleLetterCost;
-        const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
-                terminalDicNode->getFlags(), terminalDicNode->getAttributesPos());
         const bool isPossiblyOffensiveWord = terminalDicNode->getProbability() <= 0;
         const bool isExactMatch = terminalDicNode->isExactMatch();
         const bool isFirstCharUppercase = terminalDicNode->isFirstCharUppercase();
@@ -176,60 +182,43 @@
                 | (isSafeExactMatch ? Dictionary::KIND_FLAG_EXACT_MATCH : 0);
 
         // Entries that are blacklisted or do not represent a word should not be output.
-        const bool isValidWord = !terminalAttributes.isBlacklistedOrNotAWord();
+        const bool isValidWord = !terminalDicNode->isBlacklistedOrNotAWord();
 
         // Increase output score of top typing suggestion to ensure autocorrection.
         // TODO: Better integration with java side autocorrection logic.
-        // Force autocorrection for obvious long multi-word suggestions.
-        const bool isForceCommitMultiWords = TRAVERSAL->allowPartialCommit()
-                && (traverseSession->isPartiallyCommited()
-                        || (traverseSession->getInputSize() >= MIN_LEN_FOR_MULTI_WORD_AUTOCORRECT
-                                && terminalDicNode->hasMultipleWords()));
-
         const int finalScore = SCORING->calculateFinalScore(
                 compoundDistance, traverseSession->getInputSize(),
-                isForceCommitMultiWords || (isValidWord && SCORING->doesAutoCorrectValidWord()));
+                terminalDicNode->isExactMatch()
+                        || (forceCommitMultiWords && terminalDicNode->hasMultipleWords())
+                                || (isValidWord && SCORING->doesAutoCorrectValidWord()));
         maxScore = max(maxScore, finalScore);
 
-        if (TRAVERSAL->allowPartialCommit()) {
-            // Index for top typing suggestion should be 0.
-            if (isValidWord && outputWordIndex == 0) {
-                terminalDicNode->outputSpacePositionsResult(spaceIndices);
-            }
+        // TODO: Implement a smarter auto-commit method for handling multi-word suggestions.
+        // Index for top typing suggestion should be 0.
+        if (isValidWord && outputWordIndex == 0) {
+            terminalDicNode->outputSpacePositionsResult(spaceIndices);
         }
 
         // Don't output invalid words. However, we still need to submit their shortcuts if any.
         if (isValidWord) {
             outputTypes[outputWordIndex] = Dictionary::KIND_CORRECTION | outputTypeFlags;
             frequencies[outputWordIndex] = finalScore;
-            if (isSafeExactMatch) {
-                // Demote exact matches that are not the highest probable node among all exact
-                // matches.
-                const bool isBestTerminal = bestExactMatchedNodeTerminalIndex < 0
-                        || terminals[bestExactMatchedNodeTerminalIndex].getProbability()
-                                < terminalDicNode->getProbability();
-                const int outputWordIndexToBeDemoted = isBestTerminal ?
-                        bestExactMatchedNodeOutputWordIndex : outputWordIndex;
-                if (outputWordIndexToBeDemoted >= 0) {
-                    frequencies[outputWordIndexToBeDemoted] -=
-                            FINAL_SCORE_PENALTY_FOR_NOT_BEST_EXACT_MATCHED_WORD;
-                }
-                if (isBestTerminal) {
-                    // Updates the best exact matched node index.
-                    bestExactMatchedNodeTerminalIndex = terminalIndex;
-                    // Updates the best exact matched output word index.
-                    bestExactMatchedNodeOutputWordIndex = outputWordIndex;
-                }
-            }
             // Populate the outputChars array with the suggested word.
             const int startIndex = outputWordIndex * MAX_WORD_LENGTH;
             terminalDicNode->outputResult(&outputCodePoints[startIndex]);
             ++outputWordIndex;
         }
 
-        const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
-        outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
-                finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+        if (!terminalDicNode->hasMultipleWords()) {
+            const TerminalAttributes terminalAttributes(traverseSession->getBinaryDictionaryInfo(),
+                    terminalDicNode->getAttributesPos());
+            // Shortcut is not supported for multiple words suggestions.
+            // TODO: Check shortcuts during traversal for multiple words suggestions.
+            const bool sameAsTyped = TRAVERSAL->sameAsTyped(traverseSession, terminalDicNode);
+            outputWordIndex = ShortcutUtils::outputShortcuts(&terminalAttributes, outputWordIndex,
+                    finalScore, outputCodePoints, frequencies, outputTypes, sameAsTyped);
+
+        }
         DicNode::managedDelete(terminalDicNode);
     }
 
diff --git a/native/jni/src/suggest/core/suggest.h b/native/jni/src/suggest/core/suggest.h
index 752bde9..875cbe4 100644
--- a/native/jni/src/suggest/core/suggest.h
+++ b/native/jni/src/suggest/core/suggest.h
@@ -82,8 +82,6 @@
 
     // Threshold for autocorrection classifier
     static const float AUTOCORRECT_CLASSIFICATION_THRESHOLD;
-    // Final score penalty to exact match words that are not the most probable exact match.
-    static const int FINAL_SCORE_PENALTY_FOR_NOT_BEST_EXACT_MATCHED_WORD;
 
     const Traversal *const TRAVERSAL;
     const Scoring *const SCORING;
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
index 5070651..70dad67 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
+++ b/native/jni/src/suggest/policyimpl/dictionary/dictionary_structure_policy_factory.h
@@ -30,10 +30,11 @@
     static const DictionaryStructurePolicy *getDictionaryStructurePolicy(
             const BinaryDictionaryFormatUtils::FORMAT_VERSION dictionaryFormat) {
         switch (dictionaryFormat) {
-            case BinaryDictionaryFormatUtils::VERSION_1:
-                // Fall through
             case BinaryDictionaryFormatUtils::VERSION_2:
                 return PatriciaTriePolicy::getInstance();
+            case BinaryDictionaryFormatUtils::VERSION_3:
+                // TODO: support version 3 dictionaries.
+                return 0;
             default:
                 ASSERT(false);
                 return 0;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
index 5ae396e..ef144e0 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_traversal.h
@@ -23,6 +23,7 @@
 #include "suggest/core/dicnode/dic_node.h"
 #include "suggest/core/dicnode/dic_node_vector.h"
 #include "suggest/core/layout/proximity_info_state.h"
+#include "suggest/core/layout/proximity_info_utils.h"
 #include "suggest/core/policy/traversal.h"
 #include "suggest/core/session/dic_traverse_session.h"
 #include "suggest/policyimpl/typing/scoring_params.h"
@@ -136,7 +137,7 @@
         return ScoringParams::MAX_SPATIAL_DISTANCE;
     }
 
-    AK_FORCE_INLINE bool allowPartialCommit() const {
+    AK_FORCE_INLINE bool autoCorrectsToMultiWordSuggestionIfTop() const {
         return true;
     }
 
@@ -159,7 +160,7 @@
             const DicNode *const dicNode) const {
         const ProximityType proximityType =
                 getProximityType(traverseSession, parentDicNode, dicNode);
-        if (!DicNodeUtils::isProximityChar(proximityType)) {
+        if (!ProximityInfoUtils::isMatchOrProximityChar(proximityType)) {
             return false;
         }
         return true;
diff --git a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
index e098f35..830aa80 100644
--- a/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
+++ b/native/jni/src/suggest/policyimpl/typing/typing_weighting.h
@@ -169,12 +169,7 @@
 
     float getTerminalLanguageCost(const DicTraverseSession *const traverseSession,
             const DicNode *const dicNode, const float dicNodeLanguageImprobability) const {
-        // We promote exact matches here to prevent them from being pruned. The final score of
-        // exact match nodes might be demoted later in Suggest::outputSuggestions if there are
-        // multiple exact matches.
-        const float languageImprobability = (dicNode->isExactMatch()) ?
-                0.0f : dicNodeLanguageImprobability;
-        return languageImprobability * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
+        return dicNodeLanguageImprobability * ScoringParams::DISTANCE_WEIGHT_LANGUAGE;
     }
 
     AK_FORCE_INLINE bool needsToNormalizeCompoundDistance() const {
diff --git a/native/jni/src/utils/log_utils.cpp b/native/jni/src/utils/log_utils.cpp
new file mode 100644
index 0000000..5ab2b28
--- /dev/null
+++ b/native/jni/src/utils/log_utils.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2013, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "log_utils.h"
+
+#include <cstdio>
+#include <stdarg.h>
+
+#include "defines.h"
+
+namespace latinime {
+    /* static */ void LogUtils::logToJava(JNIEnv *const env, const char *const format, ...) {
+        static const char *TAG = "LatinIME:LogUtils";
+        const jclass androidUtilLogClass = env->FindClass("android/util/Log");
+        if (!androidUtilLogClass) {
+            // If we can't find the class, we are probably in off-device testing, and
+            // it's expected. Regardless, logging is not essential to functionality, so
+            // we should just return. However, FindClass has thrown an exception behind
+            // our back and there is no way to prevent it from doing that, so we clear
+            // the exception before we return.
+            env->ExceptionClear();
+            return;
+        }
+        const jmethodID logDotIMethodId = env->GetStaticMethodID(androidUtilLogClass, "i",
+                "(Ljava/lang/String;Ljava/lang/String;)I");
+        if (!logDotIMethodId) {
+            env->ExceptionClear();
+            if (androidUtilLogClass) env->DeleteLocalRef(androidUtilLogClass);
+            return;
+        }
+        const jstring javaTag = env->NewStringUTF(TAG);
+
+        static const int DEFAULT_LINE_SIZE = 128;
+        char fixedSizeCString[DEFAULT_LINE_SIZE];
+        va_list argList;
+        va_start(argList, format);
+        // Get the necessary size. Add 1 for the 0 terminator.
+        const int size = vsnprintf(fixedSizeCString, DEFAULT_LINE_SIZE, format, argList) + 1;
+        va_end(argList);
+
+        jstring javaString;
+        if (size <= DEFAULT_LINE_SIZE) {
+            // The buffer was large enough.
+            javaString = env->NewStringUTF(fixedSizeCString);
+        } else {
+            // The buffer was not large enough.
+            va_start(argList, format);
+            char variableSizeCString[size];
+            vsnprintf(variableSizeCString, size, format, argList);
+            va_end(argList);
+            javaString = env->NewStringUTF(variableSizeCString);
+        }
+
+        env->CallStaticIntMethod(androidUtilLogClass, logDotIMethodId, javaTag, javaString);
+        if (javaString) env->DeleteLocalRef(javaString);
+        if (javaTag) env->DeleteLocalRef(javaTag);
+        if (androidUtilLogClass) env->DeleteLocalRef(androidUtilLogClass);
+    }
+}
diff --git a/native/jni/src/utils/log_utils.h b/native/jni/src/utils/log_utils.h
new file mode 100644
index 0000000..6ac16d9
--- /dev/null
+++ b/native/jni/src/utils/log_utils.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_LOG_UTILS_H
+#define LATINIME_LOG_UTILS_H
+
+#include "defines.h"
+#include "jni.h"
+
+namespace latinime {
+
+class LogUtils {
+ public:
+    static void logToJava(JNIEnv *const env, const char *const format, ...)
+#ifdef __GNUC__
+        __attribute__ ((format (printf, 2, 3)))
+#endif // __GNUC__
+        ;
+
+ private:
+    DISALLOW_COPY_AND_ASSIGN(LogUtils);
+};
+} // namespace latinime
+#endif // LATINIME_LOG_UTILS_H
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
index b193e66..9ad81c0 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateMultiTouchTests.java
@@ -60,7 +60,7 @@
     // Chording input in shift locked.
     public void testChordingShiftLocked() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press shift key and hold, enter into choring shift state.
@@ -119,7 +119,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -137,7 +137,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -196,7 +196,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -216,7 +216,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -397,29 +397,29 @@
 
     public void testLongPressShiftAndChording() {
         // Long press shift key, enter maybe shift locked.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet (not shift locked).
         releaseKey(CODE_SHIFT, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Long press shift key, enter maybe alphabet.
-        longPressKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
+        longPressShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED);
         // Release shift key, back to shift locked (not alphabet).
         releaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCKED);
         // Long press shift key, enter alphabet
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release shift key, enter alphabet shifted.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Long press shift key, enter maybe alphabet.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet shifted (not alphabet).
@@ -430,7 +430,7 @@
         // Load keyboard, should be in automatic shifted.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
         // Long press shift key, enter maybe shift locked.
-        longPressKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
+        longPressShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Press/release letter key, remain in manual shifted.
         chordingPressAndReleaseKey('A', ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Release shift key, back to alphabet (not shift locked).
@@ -449,7 +449,7 @@
 //        releaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED);
 //
 //        // Long press shift key, enter alphabet shift locked.
-//        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+//        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
 //                ALPHABET_SHIFT_LOCKED);
 //        // First shift key tap.
 //        pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
index d5b9d1d..c7ac76d 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateSingleTouchTests.java
@@ -91,7 +91,7 @@
     // Switching between alphabet shift locked and symbols.
     public void testAlphabetShiftLockedAndSymbols() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press/release "?123" key, enter into symbols.
@@ -133,7 +133,7 @@
     // Automatic switch back to alphabet shift locked test by space key.
     public void testSwitchBackBySpaceShiftLocked() {
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
 
         // Press/release "?123" key, enter into symbols.
@@ -196,13 +196,13 @@
         // Load keyboard, should be in alphabet.
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release letter key, remain in shift locked.
         pressAndReleaseKey('A', ALPHABET_SHIFT_LOCKED, ALPHABET_SHIFT_LOCKED);
@@ -212,16 +212,16 @@
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release shift key, enter alphabet shifted.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
@@ -231,7 +231,7 @@
         // Load keyboard, should be in automatic shifted.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key, back to alphabet.
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
@@ -293,12 +293,12 @@
         updateShiftState(ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Update shift state, remained in alphabet shift locked.
         updateShiftState(ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Press/release "?123" key, enter into symbols.
@@ -326,12 +326,12 @@
         updateShiftState(ALPHABET_AUTOMATIC_SHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Update shift state, remained in alphabet shift locked (not automatic shifted).
         updateShiftState(ALPHABET_SHIFT_LOCKED);
         // Long press shift key, back to alphabet.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_SHIFT_LOCK_SHIFTED,
                 ALPHABET_UNSHIFTED);
 
         // Load keyboard, should be in automatic shifted.
@@ -383,7 +383,7 @@
 
         // Alphabet shift locked -> shift key + letter -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press and slide from "123?" key, enter symbols.
         pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -441,7 +441,7 @@
 
         // Alphabet shift locked -> shift key + letter -> cancel -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press and slide from "123?" key, enter symbols.
         pressAndSlideFromKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -500,7 +500,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -517,7 +517,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -574,7 +574,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -592,7 +592,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -651,7 +651,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -670,7 +670,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -733,7 +733,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -753,7 +753,7 @@
         // Load keyboard
         loadKeyboard(ALPHABET_UNSHIFTED);
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release "?123" key, enter into symbols.
         pressAndReleaseKey(CODE_SYMBOL, SYMBOLS_UNSHIFTED, SYMBOLS_UNSHIFTED);
@@ -777,7 +777,7 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Change focus to new text field.
         loadKeyboard(ALPHABET_UNSHIFTED);
@@ -808,7 +808,7 @@
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
 
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Change focus to new text field.
         loadKeyboard(ALPHABET_AUTOMATIC_SHIFTED);
@@ -852,7 +852,7 @@
 
         // Alphabet shift locked -> rotate -> alphabet shift locked.
         // Long press shift key, enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Rotate device, remain in alphabet shift locked.
         rotateDevice(ALPHABET_SHIFT_LOCKED);
@@ -936,7 +936,7 @@
         secondPressAndReleaseKey('J', ALPHABET_MANUAL_SHIFTED, ALPHABET_UNSHIFTED);
 
         // Long press shift key to enter alphabet shift locked.
-        longPressAndReleaseKey(CODE_SHIFT, ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
+        longPressAndReleaseShiftKey(ALPHABET_MANUAL_SHIFTED, ALPHABET_MANUAL_SHIFTED,
                 ALPHABET_SHIFT_LOCKED);
         // Press/release shift key
         pressAndReleaseKey(CODE_SHIFT, ALPHABET_SHIFT_LOCK_SHIFTED, ALPHABET_UNSHIFTED);
diff --git a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
index 6991d05..3ffd0a9 100644
--- a/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
+++ b/tests/src/com/android/inputmethod/keyboard/internal/KeyboardStateTestsBase.java
@@ -34,6 +34,11 @@
         loadKeyboard(ALPHABET_UNSHIFTED);
     }
 
+    /**
+     * Set auto caps mode.
+     *
+     * @param autoCaps the auto cap mode.
+     */
     public void setAutoCapsMode(final int autoCaps) {
         mSwitcher.setAutoCapsMode(autoCaps);
     }
@@ -44,17 +49,32 @@
                 expected == actual);
     }
 
+    /**
+     * Emulate update keyboard shift state.
+     *
+     * @param afterUpdate the keyboard state after updating the keyboard shift state.
+     */
     public void updateShiftState(final int afterUpdate) {
         mSwitcher.updateShiftState();
         assertLayout("afterUpdate", afterUpdate, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate load default keyboard.
+     *
+     * @param afterLoad the keyboard state after loading default keyboard.
+     */
     public void loadKeyboard(final int afterLoad) {
         mSwitcher.loadKeyboard();
         mSwitcher.updateShiftState();
         assertLayout("afterLoad", afterLoad, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate rotate device.
+     *
+     * @param afterRotate the keyboard state after rotating device.
+     */
     public void rotateDevice(final int afterRotate) {
         mSwitcher.saveKeyboardState();
         mSwitcher.loadKeyboard();
@@ -67,45 +87,97 @@
         assertLayout("afterPress", afterPress, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate key press.
+     *
+     * @param code the key code to press.
+     * @param afterPress the keyboard state after pressing the key.
+     */
     public void pressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
+    /**
+     * Emulate key release and register.
+     *
+     * @param code the key code to release and register
+     * @param afterRelease the keyboard state after releasing the key.
+     */
     public void releaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate key press and release.
+     *
+     * @param code the key code to press and release.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterRelease the keyboard state after releasing the key.
+     */
     public void pressAndReleaseKey(final int code, final int afterPress, final int afterRelease) {
         pressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
 
+    /**
+     * Emulate chording key press.
+     *
+     * @param code the chording key code.
+     * @param afterPress the keyboard state after pressing chording key.
+     */
     public void chordingPressKey(final int code, final int afterPress) {
         mSwitcher.expireDoubleTapTimeout();
         pressKeyWithoutTimerExpire(code, false, afterPress);
     }
 
+    /**
+     * Emulate chording key release.
+     *
+     * @param code the cording key code.
+     * @param afterRelease the keyboard state after releasing chording key.
+     */
     public void chordingReleaseKey(final int code, final int afterRelease) {
         mSwitcher.onCodeInput(code);
         mSwitcher.onReleaseKey(code, NOT_SLIDING);
         assertLayout("afterRelease", afterRelease, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate chording key press and release.
+     *
+     * @param code the chording key code.
+     * @param afterPress the keyboard state after pressing chording key.
+     * @param afterRelease the keyboard state after releasing chording key.
+     */
     public void chordingPressAndReleaseKey(final int code, final int afterPress,
             final int afterRelease) {
         chordingPressKey(code, afterPress);
         chordingReleaseKey(code, afterRelease);
     }
 
+    /**
+     * Emulate start of the sliding key input.
+     *
+     * @param code the key code to start sliding.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterSlide the keyboard state after releasing the key with sliding input.
+     */
     public void pressAndSlideFromKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onReleaseKey(code, SLIDING);
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate end of the sliding key input.
+     *
+     * @param code the key code to stop sliding.
+     * @param afterPress the keyboard state after pressing the key.
+     * @param afterSlide the keyboard state after releasing the key and stop sliding.
+     */
     public void stopSlidingOnKey(final int code, final int afterPress, final int afterSlide) {
         pressKey(code, afterPress);
         mSwitcher.onCodeInput(code);
@@ -114,32 +186,67 @@
         assertLayout("afterSlide", afterSlide, mSwitcher.getLayoutId());
     }
 
+    /**
+     * Emulate cancel the sliding key input.
+     *
+     * @param afterCancelSliding the keyboard state after canceling sliding input.
+     */
     public void stopSlidingAndCancel(final int afterCancelSliding) {
         mSwitcher.onFinishSlidingInput();
         assertLayout("afterCancelSliding", afterCancelSliding, mSwitcher.getLayoutId());
     }
 
-    public void longPressKey(final int code, final int afterPress, final int afterLongPress) {
-        pressKey(code, afterPress);
+    /**
+     * Emulate long press shift key.
+     *
+     * @param afterPress the keyboard state after pressing shift key.
+     * @param afterLongPress the keyboard state after long press fired.
+     */
+    public void longPressShiftKey(final int afterPress, final int afterLongPress) {
         // Long press shift key will register {@link Constants#CODE_CAPS_LOCK}. See
         // {@link R.xml#key_styles_common} and its baseForShiftKeyStyle. We thus emulate the
-        // behavior here.
-        final int longPressCode = code == CODE_SHIFT ? Constants.CODE_CAPSLOCK : code;
-        mSwitcher.onCodeInput(longPressCode);
+        // behavior that is implemented in {@link MainKeyboardView#onLongPress(PointerTracker)}.
+        pressKey(Constants.CODE_SHIFT, afterPress);
+        mSwitcher.onPressKey(Constants.CODE_CAPSLOCK, true /* isSinglePointer */);
+        mSwitcher.onCodeInput(Constants.CODE_CAPSLOCK);
         assertLayout("afterLongPress", afterLongPress, mSwitcher.getLayoutId());
     }
 
-    public void longPressAndReleaseKey(final int code, final int afterPress,
-            final int afterLongPress, final int afterRelease) {
-        longPressKey(code, afterPress, afterLongPress);
-        releaseKey(code, afterRelease);
+    /**
+     * Emulate long press shift key and release.
+     *
+     * @param afterPress the keyboard state after pressing shift key.
+     * @param afterLongPress the keyboard state after long press fired.
+     * @param afterRelease the keyboard state after shift key is released.
+     */
+    public void longPressAndReleaseShiftKey(final int afterPress, final int afterLongPress,
+            final int afterRelease) {
+        // Long press shift key will register {@link Constants#CODE_CAPS_LOCK}. See
+        // {@link R.xml#key_styles_common} and its baseForShiftKeyStyle. We thus emulate the
+        // behavior that is implemented in {@link MainKeyboardView#onLongPress(PointerTracker)}.
+        longPressShiftKey(afterPress, afterLongPress);
+        releaseKey(Constants.CODE_CAPSLOCK, afterRelease);
     }
 
-    public void secondPressKey(int code, int afterPress) {
+    /**
+     * Emulate the second press of the double tap.
+     *
+     * @param code the key code to double tap.
+     * @param afterPress the keyboard state after pressing the second tap.
+     */
+    public void secondPressKey(final int code, final int afterPress) {
         pressKeyWithoutTimerExpire(code, true, afterPress);
     }
 
-    public void secondPressAndReleaseKey(int code, int afterPress, int afterRelease) {
+    /**
+     * Emulate the second tap of the double tap.
+     *
+     * @param code the key code to double tap.
+     * @param afterPress the keyboard state after pressing the second tap.
+     * @param afterRelease the keyboard state after releasing the second tap.
+     */
+    public void secondPressAndReleaseKey(final int code, final int afterPress,
+            final int afterRelease) {
         secondPressKey(code, afterPress);
         releaseKey(code, afterRelease);
     }
diff --git a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
index 0e077bb..c0dd993 100644
--- a/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
+++ b/tests/src/com/android/inputmethod/latin/RichInputConnectionAndTextRangeTests.java
@@ -16,6 +16,8 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.latin.utils.TextRange;
+
 import android.inputmethodservice.InputMethodService;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
@@ -30,8 +32,6 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionWrapper;
 
-import com.android.inputmethod.latin.RichInputConnection.Range;
-
 import java.util.Locale;
 
 @SmallTest
@@ -169,7 +169,7 @@
         mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
         et.startOffset = 0;
         et.selectionStart = 7;
-        Range r;
+        TextRange r;
 
         ic.beginBatchEdit();
         // basic case
@@ -241,7 +241,7 @@
         text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
                 10 /* start */, 16 /* end */, 0 /* flags */);
         mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
-        Range r;
+        TextRange r;
         SuggestionSpan[] suggestions;
 
         r = ic.getWordRangeAtCursor(" ", 0);
diff --git a/tests/src/com/android/inputmethod/latin/WordComposerTests.java b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
new file mode 100644
index 0000000..1434c6b
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/WordComposerTests.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit tests for WordComposer.
+ */
+@SmallTest
+public class WordComposerTests extends AndroidTestCase {
+    public void testMoveCursor() {
+        final WordComposer wc = new WordComposer();
+        final String STR_WITHIN_BMP = "abcdef";
+        wc.setComposingWord(STR_WITHIN_BMP, null);
+        assertEquals(wc.size(),
+                STR_WITHIN_BMP.codePointCount(0, STR_WITHIN_BMP.length()));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        wc.setCursorPositionWithinWord(2);
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor to after the 'd'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(2));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor to after the 'e'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertEquals(wc.size(), 6);
+        // Move the cursor to after the 'f'
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        // Move the cursor past the end of the word
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(15));
+
+        // \uD861\uDED7 is 𨛗, a character outside the BMP
+        final String STR_WITH_SUPPLEMENTARY_CHAR = "abcde\uD861\uDED7fgh";
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertEquals(wc.size(), STR_WITH_SUPPLEMENTARY_CHAR.codePointCount(0,
+                        STR_WITH_SUPPLEMENTARY_CHAR.length()));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(6));
+        assertTrue(wc.isCursorFrontOrMiddleOfComposingWord());
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(1));
+        assertFalse(wc.isCursorFrontOrMiddleOfComposingWord());
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(7));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-3));
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-1));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(3);
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-9));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(-10));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertFalse(wc.moveCursorByAndReturnIfInsideComposingWord(-11));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
+
+        wc.setComposingWord(STR_WITH_SUPPLEMENTARY_CHAR, null);
+        wc.setCursorPositionWithinWord(2);
+        assertTrue(wc.moveCursorByAndReturnIfInsideComposingWord(0));
+    }
+}
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
index cca81a0..55f1632 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOTests.java
@@ -51,7 +51,8 @@
 @LargeTest
 public class BinaryDictIOTests extends AndroidTestCase {
     private static final String TAG = BinaryDictIOTests.class.getSimpleName();
-    private static final int MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_MAX_UNIGRAMS = 100;
+    private static final int DEFAULT_CODE_POINT_SET_SIZE = 50;
     private static final int UNIGRAM_FREQ = 10;
     private static final int BIGRAM_FREQ = 50;
     private static final int TOLERANCE_OF_BIGRAM_FREQ = 5;
@@ -73,13 +74,16 @@
             new FormatSpec.FormatOptions(3, true /* supportsDynamicUpdate */);
 
     public BinaryDictIOTests() {
-        super();
+        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
+    }
 
-        final long time = System.currentTimeMillis();
-        Log.e(TAG, "Testing dictionary: seed is " + time);
-        final Random random = new Random(time);
+    public BinaryDictIOTests(final long seed, final int maxUnigrams) {
+        super();
+        Log.e(TAG, "Testing dictionary: seed is " + seed);
+        final Random random = new Random(seed);
         sWords.clear();
-        generateWords(MAX_UNIGRAMS, random);
+        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
+        generateWords(maxUnigrams, random, codePointSet);
 
         for (int i = 0; i < sWords.size(); ++i) {
             sChainBigrams.put(i, new ArrayList<Integer>());
@@ -94,6 +98,23 @@
         }
     }
 
+    private int[] generateCodePointSet(final int codePointSetSize, final Random random) {
+        final int[] codePointSet = new int[codePointSetSize];
+        for (int i = codePointSet.length - 1; i >= 0; ) {
+            final int r = Math.abs(random.nextInt());
+            if (r < 0) continue;
+            // Don't insert 0~0x20, but insert any other code point.
+            // Code points are in the range 0~0x10FFFF.
+            final int candidateCodePoint = (int)(0x20 + r % (Character.MAX_CODE_POINT - 0x20));
+            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
+            if (candidateCodePoint >= Character.MIN_SURROGATE
+                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
+            codePointSet[i] = candidateCodePoint;
+            --i;
+        }
+        return codePointSet;
+    }
+
     // Utilities for test
 
     /**
@@ -129,28 +150,29 @@
     /**
      * Generates a random word.
      */
-    private String generateWord(final Random random) {
-        StringBuilder builder = new StringBuilder("a");
-        int count = random.nextInt() % 30; // Arbitrarily 30 chars max
-        while (count > 0) {
-            final long r = Math.abs(random.nextInt());
-            if (r < 0) continue;
-            // Don't insert 0~0x20, but insert any other code point.
-            // Code points are in the range 0~0x10FFFF.
-            final int candidateCodePoint = (int)(0x20 + r % (Character.MAX_CODE_POINT - 0x20));
-            // Code points between MIN_ and MAX_SURROGATE are not valid on their own.
-            if (candidateCodePoint >= Character.MIN_SURROGATE
-                    && candidateCodePoint <= Character.MAX_SURROGATE) continue;
-            builder.appendCodePoint(candidateCodePoint);
-            --count;
+    private String generateWord(final Random random, final int[] codePointSet) {
+        StringBuilder builder = new StringBuilder();
+        // 8 * 4 = 32 chars max, but we do it the following way so as to bias the random toward
+        // longer words. This should be closer to natural language, and more importantly, it will
+        // exercise the algorithms in dicttool much more.
+        final int count = 1 + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5)
+                + (Math.abs(random.nextInt()) % 5);
+        while (builder.length() < count) {
+            builder.appendCodePoint(codePointSet[Math.abs(random.nextInt()) % codePointSet.length]);
         }
         return builder.toString();
     }
 
-    private void generateWords(final int number, final Random random) {
+    private void generateWords(final int number, final Random random, final int[] codePointSet) {
         final Set<String> wordSet = CollectionUtils.newHashSet();
         while (wordSet.size() < number) {
-            wordSet.add(generateWord(random));
+            wordSet.add(generateWord(random, codePointSet));
         }
         sWords.addAll(wordSet);
     }
@@ -558,8 +580,9 @@
 
         // Test a word that isn't contained within the dictionary.
         final Random random = new Random((int)System.currentTimeMillis());
+        final int[] codePointSet = generateCodePointSet(DEFAULT_CODE_POINT_SET_SIZE, random);
         for (int i = 0; i < 1000; ++i) {
-            final String word = generateWord(random);
+            final String word = generateWord(random, codePointSet);
             if (sWords.indexOf(word) != -1) continue;
             runGetTerminalPosition(buffer, word, i, false);
         }
diff --git a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
index d33142c..9331da4 100644
--- a/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
+++ b/tests/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtilsTests.java
@@ -44,9 +44,10 @@
     private static final String TAG = BinaryDictIOUtilsTests.class.getSimpleName();
     private static final FormatSpec.FormatOptions FORMAT_OPTIONS =
             new FormatSpec.FormatOptions(3, true);
-    private static final int MAX_UNIGRAMS = 1500;
 
     private static final ArrayList<String> sWords = CollectionUtils.newArrayList();
+    public static final int DEFAULT_MAX_UNIGRAMS = 1500;
+    private final int mMaxUnigrams;
 
     private static final String[] CHARACTERS = {
         "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
@@ -57,15 +58,17 @@
     };
 
     public BinaryDictIOUtilsTests() {
-        this(System.currentTimeMillis());
+        // 1500 is the default max unigrams
+        this(System.currentTimeMillis(), DEFAULT_MAX_UNIGRAMS);
     }
 
-    public BinaryDictIOUtilsTests(final long seed) {
+    public BinaryDictIOUtilsTests(final long seed, final int maxUnigrams) {
         super();
-        Log.d(TAG, "Seed for test is " + seed);
+        Log.d(TAG, "Seed for test is " + seed + ", maxUnigrams is " + maxUnigrams);
+        mMaxUnigrams = maxUnigrams;
         final Random random = new Random(seed);
         sWords.clear();
-        for (int i = 0; i < MAX_UNIGRAMS; ++i) {
+        for (int i = 0; i < maxUnigrams; ++i) {
             sWords.add(generateWord(random.nextInt()));
         }
     }
@@ -395,6 +398,6 @@
 
         Log.d(TAG, "max = " + ((double)maxTimeToInsert/1000000) + " ms.");
         Log.d(TAG, "min = " + ((double)minTimeToInsert/1000000) + " ms.");
-        Log.d(TAG, "avg = " + ((double)sum/MAX_UNIGRAMS/1000000) + " ms.");
+        Log.d(TAG, "avg = " + ((double)sum/mMaxUnigrams/1000000) + " ms.");
     }
 }
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 7b311c3..cacee52 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Dicttool.java
@@ -72,15 +72,21 @@
         return command;
     }
 
-    private void execute(final String[] arguments) {
+    /**
+     * Executes the specified command with the specified arguments.
+     * @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) {
         final Command command = getCommand(arguments);
         try {
             command.run();
+            return 0;
         } catch (Exception e) {
             System.out.println("Exception while processing command "
                     + command.getClass().getSimpleName() + " : " + e);
             e.printStackTrace();
-            return;
+            return 1;
         }
     }
 
@@ -89,6 +95,7 @@
             help();
             return;
         }
-        new Dicttool().execute(arguments);
+        // Exit with the success/error code from #execute() as status.
+        System.exit(new Dicttool().execute(arguments));
     }
 }
diff --git a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
index 827c5e3..972b6e7 100644
--- a/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
+++ b/tools/dicttool/src/com/android/inputmethod/latin/dicttool/Test.java
@@ -16,36 +16,93 @@
 
 package com.android.inputmethod.latin.dicttool;
 
+import com.android.inputmethod.latin.makedict.BinaryDictIOTests;
 import com.android.inputmethod.latin.makedict.BinaryDictIOUtilsTests;
 import com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest;
 import com.android.inputmethod.latin.makedict.FusionDictionaryTest;
-import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 
-import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
 
 /**
  * Dicttool command implementing self-tests.
  */
 public class Test extends Dicttool.Command {
     public static final String COMMAND = "test";
+    private long mSeed = System.currentTimeMillis();
+    private int mMaxUnigrams = BinaryDictIOUtilsTests.DEFAULT_MAX_UNIGRAMS;
+
+    private static final Class<?>[] sClassesToTest = {
+        BinaryDictOffdeviceUtilsTests.class,
+        FusionDictionaryTest.class,
+        BinaryDictInputOutputTest.class,
+        BinaryDictIOUtilsTests.class,
+        BinaryDictIOTests.class
+    };
+    private ArrayList<Method> mAllTestMethods = new ArrayList<Method>();
+    private ArrayList<String> mUsedTestMethods = new ArrayList<String>();
 
     public Test() {
+        for (final Class<?> c : sClassesToTest) {
+            for (final Method m : c.getDeclaredMethods()) {
+                if (m.getName().startsWith("test") && Void.TYPE == m.getReturnType()
+                        && 0 == m.getParameterTypes().length) {
+                    mAllTestMethods.add(m);
+                }
+            }
+        }
     }
 
     @Override
     public String getHelp() {
-        return "test";
+        final StringBuilder s = new StringBuilder("test [-s seed] [-m maxUnigrams] [testName...]\n"
+                + "If seed is not specified, the current time is used.\nTest list is:\n");
+        for (final Method m : mAllTestMethods) {
+            s.append("  ");
+            s.append(m.getName());
+            s.append("\n");
+        }
+        return s.toString();
     }
 
     @Override
-    public void run() throws IOException, UnsupportedFormatException {
-        test();
+    public void run() throws IllegalAccessException, InstantiationException,
+            InvocationTargetException {
+        int i = 0;
+        while (i < mArgs.length) {
+            final String arg = mArgs[i++];
+            if ("-s".equals(arg)) {
+                mSeed = Long.parseLong(mArgs[i++]);
+            } else if ("-m".equals(arg)) {
+                mMaxUnigrams = Integer.parseInt(mArgs[i++]);
+            } else {
+                mUsedTestMethods.add(arg);
+            }
+        }
+        runChosenTests();
     }
 
-    private void test() throws IOException, UnsupportedFormatException {
-        new BinaryDictOffdeviceUtilsTests().testGetRawDictWorks();
-        new FusionDictionaryTest().testFusion();
-        new BinaryDictInputOutputTest().testFlattenNodes();
-        new BinaryDictIOUtilsTests().testRandomWords();
+    private void runChosenTests() throws IllegalAccessException, InstantiationException,
+            InvocationTargetException {
+        for (final Method m : mAllTestMethods) {
+            final Class<?> declaringClass = m.getDeclaringClass();
+            if (!mUsedTestMethods.isEmpty() && !mUsedTestMethods.contains(m.getName())) continue;
+            // Some of the test classes expose a two-argument constructor, taking a long as a
+            // seed for Random, and an int for a vocabulary size to test the dictionary with. They
+            // correspond respectively to the -s and -m numerical arguments to this command, which
+            // are stored in mSeed and mMaxUnigrams. If the two-arguments constructor is present,
+            // then invoke it; otherwise, invoke the default constructor.
+            Constructor<?> twoArgsConstructor = null;
+            try {
+                twoArgsConstructor = declaringClass.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
+            } catch (NoSuchMethodException e) {
+                // No constructor with two args
+            }
+            final Object instance = null == twoArgsConstructor ? declaringClass.newInstance()
+                    : twoArgsConstructor.newInstance(mSeed, mMaxUnigrams);
+            m.invoke(instance);
+        }
     }
 }
diff --git a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
index 2fc97b5..479a766 100644
--- a/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
+++ b/tools/maketext/res/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.tmpl
@@ -20,7 +20,7 @@
 import android.content.res.Resources;
 
 import com.android.inputmethod.annotations.UsedForTesting;
-import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.utils.CollectionUtils;
 
 import java.util.HashMap;
 
diff --git a/tools/maketext/res/values-az/donottranslate-more-keys.xml b/tools/maketext/res/values-az/donottranslate-more-keys.xml
new file mode 100644
index 0000000..db1784c
--- /dev/null
+++ b/tools/maketext/res/values-az/donottranslate-more-keys.xml
@@ -0,0 +1,59 @@
+<?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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+00E2: "â" LATIN SMALL LETTER A WITH CIRCUMFLEX -->
+    <string name="more_keys_for_a">&#x00E2;</string>
+    <!-- U+0259: "ə" LATIN SMALL LETTER SCHWA -->
+    <string name="more_keys_for_e">&#x0259;</string>
+    <!-- U+0131: "ı" LATIN SMALL LETTER DOTLESS I
+         U+00EE: "î" LATIN SMALL LETTER I WITH CIRCUMFLEX
+         U+00EF: "ï" LATIN SMALL LETTER I WITH DIAERESIS
+         U+00EC: "ì" LATIN SMALL LETTER I WITH GRAVE
+         U+00ED: "í" LATIN SMALL LETTER I WITH ACUTE
+         U+012F: "į" LATIN SMALL LETTER I WITH OGONEK
+         U+012B: "ī" LATIN SMALL LETTER I WITH MACRON -->
+    <string name="more_keys_for_i">&#x0131;,&#x00EE;,&#x00EF;,&#x00EC;,&#x00ED;,&#x012F;,&#x012B;</string>
+    <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
+         U+00F4: "ô" LATIN SMALL LETTER O WITH CIRCUMFLEX
+         U+0153: "œ" LATIN SMALL LIGATURE OE
+         U+00F2: "ò" LATIN SMALL LETTER O WITH GRAVE
+         U+00F3: "ó" LATIN SMALL LETTER O WITH ACUTE
+         U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
+         U+00F8: "ø" LATIN SMALL LETTER O WITH STROKE
+         U+014D: "ō" LATIN SMALL LETTER O WITH MACRON -->
+    <string name="more_keys_for_o">&#x00F6;,&#x00F4;,&#x0153;,&#x00F2;,&#x00F3;,&#x00F5;,&#x00F8;,&#x014D;</string>
+    <!-- U+00FC: "ü" LATIN SMALL LETTER U WITH DIAERESIS
+         U+00FB: "û" LATIN SMALL LETTER U WITH CIRCUMFLEX
+         U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
+         U+00FA: "ú" LATIN SMALL LETTER U WITH ACUTE
+         U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
+    <string name="more_keys_for_u">&#x00FC;,&#x00FB;,&#x00F9;,&#x00FA;,&#x016B;</string>
+    <!-- U+015F: "ş" LATIN SMALL LETTER S WITH CEDILLA
+         U+00DF: "ß" LATIN SMALL LETTER SHARP S
+         U+015B: "ś" LATIN SMALL LETTER S WITH ACUTE
+         U+0161: "š" LATIN SMALL LETTER S WITH CARON -->
+    <string name="more_keys_for_s">&#x015F;,&#x00DF;,&#x015B;,&#x0161;</string>
+    <!-- U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
+    <string name="more_keys_for_g">&#x011F;</string>
+    <!-- U+00E7: "ç" LATIN SMALL LETTER C WITH CEDILLA
+         U+0107: "ć" LATIN SMALL LETTER C WITH ACUTE
+         U+010D: "č" LATIN SMALL LETTER C WITH CARON -->
+    <string name="more_keys_for_c">&#x00E7;,&#x0107;,&#x010D;</string>
+</resources>
diff --git a/tools/maketext/res/values-kk/donottranslate-more-keys.xml b/tools/maketext/res/values-kk/donottranslate-more-keys.xml
new file mode 100644
index 0000000..0e953ff
--- /dev/null
+++ b/tools/maketext/res/values-kk/donottranslate-more-keys.xml
@@ -0,0 +1,57 @@
+<?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.
+*/
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- U+0449: "щ" CYRILLIC SMALL LETTER SHCHA -->
+    <string name="keylabel_for_east_slavic_row1_9">&#x0449;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="keylabel_for_east_slavic_row1_12">&#x044A;</string>
+    <!-- U+044B: "ы" CYRILLIC SMALL LETTER YERU -->
+    <string name="keylabel_for_east_slavic_row2_1">&#x044B;</string>
+    <!-- U+044D: "э" CYRILLIC SMALL LETTER E -->
+    <string name="keylabel_for_east_slavic_row2_11">&#x044D;</string>
+    <!-- U+0438: "и" CYRILLIC SMALL LETTER I -->
+    <string name="keylabel_for_east_slavic_row3_5">&#x0438;</string>
+    <!-- U+04AF: "ү" CYRILLIC SMALL LETTER STRAIGHT U
+         U+04B1: "ұ" CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE -->
+    <string name="more_keys_for_cyrillic_u">&#x04AF;,&#x04B1;</string>
+    <!-- U+049B: "қ" CYRILLIC SMALL LETTER KA WITH DESCENDER -->
+    <string name="more_keys_for_cyrillic_ka">&#x049B;</string>
+    <!-- U+0451: "ё" CYRILLIC SMALL LETTER IO -->
+    <string name="more_keys_for_cyrillic_ie">&#x0451;</string>
+    <!-- U+04A3: "ң" CYRILLIC SMALL LETTER EN WITH DESCENDER -->
+    <string name="more_keys_for_cyrillic_en">&#x04A3;</string>
+    <!-- U+0493: "ғ" CYRILLIC SMALL LETTER GHE WITH STROKE -->
+    <string name="more_keys_for_cyrillic_ghe">&#x0493;</string>
+    <!-- U+0456: "і" CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
+    <string name="more_keys_for_east_slavic_row2_1">&#x0456;</string>
+    <!-- U+04D9: "ә" CYRILLIC SMALL LETTER SCHWA -->
+    <string name="more_keys_for_cyrillic_a">&#x04D9;</string>
+    <!-- U+04E9: "ө" CYRILLIC SMALL LETTER BARRED O -->
+    <string name="more_keys_for_cyrillic_o">&#x04E9;</string>
+    <!-- U+04BB: "һ" CYRILLIC SMALL LETTER SHHA -->
+    <string name="more_keys_for_east_slavic_row2_11">&#x04BB;</string>
+    <!-- U+044A: "ъ" CYRILLIC SMALL LETTER HARD SIGN -->
+    <string name="more_keys_for_cyrillic_soft_sign">&#x044A;</string>
+    <!-- Label for "switch to alphabetic" key.
+         U+0410: "А" CYRILLIC CAPITAL LETTER A
+         U+0411: "Б" CYRILLIC CAPITAL LETTER BE
+         U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
+    <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+</resources>
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index c2b7513..b53a369 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -49,11 +49,14 @@
     <string name="keylabel_for_east_slavic_row2_11"></string>
     <string name="keylabel_for_east_slavic_row3_5"></string>
     <string name="more_keys_for_cyrillic_u"></string>
+    <string name="more_keys_for_cyrillic_ka"></string>
     <string name="more_keys_for_cyrillic_en"></string>
     <string name="more_keys_for_cyrillic_ghe"></string>
     <string name="more_keys_for_east_slavic_row2_1"></string>
+    <string name="more_keys_for_cyrillic_a"></string>
     <string name="more_keys_for_cyrillic_o"></string>
     <string name="more_keys_for_cyrillic_soft_sign"></string>
+    <string name="more_keys_for_east_slavic_row2_11"></string>
     <string name="keylabel_for_south_slavic_row1_6"></string>
     <string name="keylabel_for_south_slavic_row2_11"></string>
     <string name="keylabel_for_south_slavic_row3_1"></string>