keep history after reset
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b88c18e..c05b318 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.READ_USER_DICTIONARY" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
 
     <application android:label="@string/aosp_android_keyboard_ime_name"
             android:icon="@mipmap/ic_ime_settings"
@@ -50,12 +51,21 @@
 
         <activity android:name=".setup.SetupActivity"
                 android:label="@string/aosp_android_keyboard_ime_name"
-                android:icon="@mipmap/ic_ime_settings">
+                android:icon="@drawable/ic_setup_wizard">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
+        <receiver android:name=".setup.LauncherIconVisibilityManager">
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.intent.action.USER_INITIALIZE" />
+            </intent-filter>
+        </receiver>
+
         <activity android:name="SettingsActivity" android:label="@string/english_ime_settings"
                   android:uiOptions="splitActionBarWhenNarrow">
             <intent-filter>
diff --git a/java/res/drawable-hdpi/ic_settings_language.png b/java/res/drawable-hdpi/ic_settings_language.png
new file mode 100644
index 0000000..f635b2e
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/drawable-hdpi/ic_setup_wizard.png b/java/res/drawable-hdpi/ic_setup_wizard.png
new file mode 100644
index 0000000..38fca6d
--- /dev/null
+++ b/java/res/drawable-hdpi/ic_setup_wizard.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_settings_language.png b/java/res/drawable-mdpi/ic_settings_language.png
new file mode 100644
index 0000000..f8aca67
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/drawable-mdpi/ic_setup_wizard.png b/java/res/drawable-mdpi/ic_setup_wizard.png
new file mode 100644
index 0000000..66e62b8
--- /dev/null
+++ b/java/res/drawable-mdpi/ic_setup_wizard.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_settings_language.png b/java/res/drawable-xhdpi/ic_settings_language.png
new file mode 100644
index 0000000..2c42db3
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_settings_language.png
Binary files differ
diff --git a/java/res/drawable-xhdpi/ic_setup_wizard.png b/java/res/drawable-xhdpi/ic_setup_wizard.png
new file mode 100644
index 0000000..53f70a6
--- /dev/null
+++ b/java/res/drawable-xhdpi/ic_setup_wizard.png
Binary files differ
diff --git a/java/res/drawable-xxhdpi/ic_setup_wizard.png b/java/res/drawable-xxhdpi/ic_setup_wizard.png
new file mode 100644
index 0000000..6414b4f
--- /dev/null
+++ b/java/res/drawable-xxhdpi/ic_setup_wizard.png
Binary files differ
diff --git a/java/res/layout/input_view.xml b/java/res/layout/input_view.xml
index 40eff38..136e18c 100644
--- a/java/res/layout/input_view.xml
+++ b/java/res/layout/input_view.xml
@@ -43,8 +43,11 @@
             android:layout_width="@dimen/suggestions_strip_padding"
             android:layout_height="@dimen/suggestions_strip_height"
             style="?attr/suggestionsStripBackgroundStyle" />
+        <!-- To ensure that key preview popup is correctly placed when the current system locale is
+             one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
         <com.android.inputmethod.latin.suggestions.SuggestionStripView
             android:id="@+id/suggestion_strip_view"
+            android:layoutDirection="ltr"
             android:layout_weight="1.0"
             android:layout_width="0dp"
             android:layout_height="@dimen/suggestions_strip_height"
@@ -56,8 +59,11 @@
             style="?attr/suggestionsStripBackgroundStyle" />
     </LinearLayout>
 
+    <!-- To ensure that key preview popup is correctly placed when the current system locale is
+         one of RTL locales, layoutDirection="ltr" is needed in the SDK version 17+. -->
     <com.android.inputmethod.keyboard.MainKeyboardView
         android:id="@+id/keyboard_view"
+        android:layoutDirection="ltr"
         android:layout_alignParentBottom="true"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />
diff --git a/java/res/layout/setup_step.xml b/java/res/layout/setup_step.xml
new file mode 100644
index 0000000..26d7fe7
--- /dev/null
+++ b/java/res/layout/setup_step.xml
@@ -0,0 +1,58 @@
+<?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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/setup_step_title"
+        style="@style/setupStepTitleStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp" />
+    <TextView
+        android:id="@+id/setup_step_instruction"
+        style="@style/setupStepInstructionStyle"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingBottom="16dp"
+        android:paddingLeft="24dp"
+        android:paddingRight="24dp" />
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="2dp" />
+    <TextView
+        android:id="@+id/setup_step_action_label"
+        style="@style/setupStepActionLabelStyle"
+        android:gravity="center_vertical"
+        android:drawablePadding="12dp"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:clickable="true"
+        android:focusable="true"
+        android:paddingLeft="12dp"
+        android:paddingStart="12dp"
+        android:paddingRight="24dp"
+        android:paddingEnd="24dp" />
+</LinearLayout>
diff --git a/java/res/layout/setup_wizard.xml b/java/res/layout/setup_wizard.xml
new file mode 100644
index 0000000..acbbe30
--- /dev/null
+++ b/java/res/layout/setup_wizard.xml
@@ -0,0 +1,78 @@
+<?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.
+*/
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fillViewport="true">
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/setup_background"
+        android:paddingLeft="@dimen/setup_horizontal_padding"
+        android:paddingRight="@dimen/setup_horizontal_padding"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+        <TextView
+            android:id="@+id/setup_title"
+            style="@style/setupTitleStyle"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
+            android:layout_alignParentTop="true" />
+        <LinearLayout
+            android:id="@+id/setup_step_bullets"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/setup_title"
+            android:paddingTop="16dp"
+            android:orientation="horizontal">
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step1_bullet" />
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step2_bullet" />
+            <TextView
+                style="@style/setupStepBulletStyle"
+                android:text="@string/setup_step3_bullet" />
+        </LinearLayout>
+        <com.android.inputmethod.latin.setup.SetupStepIndicatorView
+            android:id="@+id/setup_step_indicator"
+            android:layout_width="match_parent"
+            android:layout_height="24dp"
+            android:layout_below="@id/setup_step_bullets" />
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/setup_step_indicator">
+            <include
+                android:id="@+id/setup_step1"
+                layout="@layout/setup_step" />
+            <include
+                android:id="@+id/setup_step2"
+                layout="@layout/setup_step" />
+            <include
+                android:id="@+id/setup_step3"
+                layout="@layout/setup_step" />
+        </FrameLayout>
+    </RelativeLayout>
+</ScrollView>
diff --git a/java/res/values-af/strings.xml b/java/res/values-af/strings.xml
index 29d9d77..4daf465 100644
--- a/java/res/values-af/strings.xml
+++ b/java/res/values-af/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Moet hierdie lêer regtig vir <xliff:g id="LOCALE_NAME">%s</xliff:g> geïnstalleer word?"</string>
     <string name="error" msgid="8940763624668513648">"Daar was \'n fout"</string>
     <string name="button_default" msgid="3988017840431881491">"Verstek"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Taal en invoer"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Kies invoermetode"</string>
 </resources>
diff --git a/java/res/values-am/strings.xml b/java/res/values-am/strings.xml
index c9bc397..d1fe341 100644
--- a/java/res/values-am/strings.xml
+++ b/java/res/values-am/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"እውን ይሄ ፋይል ለ<xliff:g id="LOCALE_NAME">%s</xliff:g> ይጫን?"</string>
     <string name="error" msgid="8940763624668513648">"ስህተት ተከስቶ ነበር"</string>
     <string name="button_default" msgid="3988017840431881491">"ነባሪ"</string>
+    <string name="language_settings" msgid="1671153053201809031">"ቋንቋ እና ግቤት"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"የግቤት ስልት ይምረጡ"</string>
 </resources>
diff --git a/java/res/values-ar/strings.xml b/java/res/values-ar/strings.xml
index 95fd99f..bca3c2a 100644
--- a/java/res/values-ar/strings.xml
+++ b/java/res/values-ar/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"هل تريد حقًا تثبيت هذا الملف للغة <xliff:g id="LOCALE_NAME">%s</xliff:g>؟"</string>
     <string name="error" msgid="8940763624668513648">"حدث خطأ"</string>
     <string name="button_default" msgid="3988017840431881491">"الافتراضية"</string>
+    <string name="language_settings" msgid="1671153053201809031">"اللغة والإدخال"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"اختيار أسلوب الإدخال"</string>
 </resources>
diff --git a/java/res/values-be/strings.xml b/java/res/values-be/strings.xml
index 6de0e0b..56382e4 100644
--- a/java/res/values-be/strings.xml
+++ b/java/res/values-be/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Сапраўды ўсталяваць гэты файл на мове: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Была памылка"</string>
     <string name="button_default" msgid="3988017840431881491">"Па змаўчанні"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Мова і ўвод"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Выберыце метад уводу"</string>
 </resources>
diff --git a/java/res/values-bg/strings.xml b/java/res/values-bg/strings.xml
index c971d7b..3ffebea 100644
--- a/java/res/values-bg/strings.xml
+++ b/java/res/values-bg/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Наистина ли да се инсталира този файл за <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Възникна грешка"</string>
     <string name="button_default" msgid="3988017840431881491">"Стандартни"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Език и въвеждане"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Избор на метод на въвеждане"</string>
 </resources>
diff --git a/java/res/values-ca/strings.xml b/java/res/values-ca/strings.xml
index 23dcda9..2652738 100644
--- a/java/res/values-ca/strings.xml
+++ b/java/res/values-ca/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Realment vols instal·lar aquest fitxer per a <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"S\'ha produït un error"</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminat"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Idioma i introducció"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Selecció de mètodes d\'introducció"</string>
 </resources>
diff --git a/java/res/values-cs/strings.xml b/java/res/values-cs/strings.xml
index 5625c74..7e12f06 100644
--- a/java/res/values-cs/strings.xml
+++ b/java/res/values-cs/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Chcete nainstalovat tento soubor pro jazyk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Došlo k chybě"</string>
     <string name="button_default" msgid="3988017840431881491">"Výchozí"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Jazyk a zadávání"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Výběr metody zadávání dat"</string>
 </resources>
diff --git a/java/res/values-da/strings.xml b/java/res/values-da/strings.xml
index 6c7936f..3ea4aaa 100644
--- a/java/res/values-da/strings.xml
+++ b/java/res/values-da/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Er du klar til at installere denne fil til <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Der opstod en fejl"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Sprog og input"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Vælg inputmetode"</string>
 </resources>
diff --git a/java/res/values-de/strings.xml b/java/res/values-de/strings.xml
index 18fb51b..a50cdf0 100644
--- a/java/res/values-de/strings.xml
+++ b/java/res/values-de/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Möchten Sie diese Datei für <xliff:g id="LOCALE_NAME">%s</xliff:g> installieren?"</string>
     <string name="error" msgid="8940763624668513648">"Es ist ein Fehler aufgetreten"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Sprache &amp; Eingabe"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Eingabemethode wählen"</string>
 </resources>
diff --git a/java/res/values-el/strings.xml b/java/res/values-el/strings.xml
index b6d9379..153dbbb 100644
--- a/java/res/values-el/strings.xml
+++ b/java/res/values-el/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Να εγκατασταθεί όντως αυτό το αρχείο για <xliff:g id="LOCALE_NAME">%s</xliff:g>;"</string>
     <string name="error" msgid="8940763624668513648">"Παρουσιάστηκε σφάλμα."</string>
     <string name="button_default" msgid="3988017840431881491">"Προεπιλογή"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Γλώσσα και εισαγωγή"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Επιλογή μεθόδου εισαγωγής"</string>
 </resources>
diff --git a/java/res/values-en-rGB/strings.xml b/java/res/values-en-rGB/strings.xml
index 01ead1b..5fbbdf6 100644
--- a/java/res/values-en-rGB/strings.xml
+++ b/java/res/values-en-rGB/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Really install this file for <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"There was an error"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Language &amp; input"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Choose input method"</string>
 </resources>
diff --git a/java/res/values-es-rUS/strings.xml b/java/res/values-es-rUS/strings.xml
index 53d7598..71841f8 100644
--- a/java/res/values-es-rUS/strings.xml
+++ b/java/res/values-es-rUS/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"¿Realmente quieres instalar este archivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Se produjo un error."</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminado"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Teclado e idioma"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Seleccionar método de entrada"</string>
 </resources>
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index 6f54dd6..9b31884 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"¿Seguro que quieres instalar este archivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Se ha producido un error"</string>
     <string name="button_default" msgid="3988017840431881491">"Predeterminado"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Idioma y entrada de texto"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Selecciona un método de entrada"</string>
 </resources>
diff --git a/java/res/values-et/strings.xml b/java/res/values-et/strings.xml
index 9cf6a9f..3364abe 100644
--- a/java/res/values-et/strings.xml
+++ b/java/res/values-et/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Kas soovite tõesti installida faili lokaadile <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ilmnes viga"</string>
     <string name="button_default" msgid="3988017840431881491">"Vaikeväärtus"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Keeled ja sisestamine"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Valige sisestusmeetod"</string>
 </resources>
diff --git a/java/res/values-fa/strings.xml b/java/res/values-fa/strings.xml
index 6821058..54ba412 100644
--- a/java/res/values-fa/strings.xml
+++ b/java/res/values-fa/strings.xml
@@ -161,4 +161,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"این فایل واقعاً برای <xliff:g id="LOCALE_NAME">%s</xliff:g> نصب شود؟"</string>
     <string name="error" msgid="8940763624668513648">"خطایی روی داد"</string>
     <string name="button_default" msgid="3988017840431881491">"پیش‌فرض"</string>
+    <string name="language_settings" msgid="1671153053201809031">"زبان و ورودی"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"انتخاب روش ورودی"</string>
 </resources>
diff --git a/java/res/values-fi/strings.xml b/java/res/values-fi/strings.xml
index 4e55232..2d75402 100644
--- a/java/res/values-fi/strings.xml
+++ b/java/res/values-fi/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Haluatko asentaa tämä tiedoston kielelle <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Tapahtui virhe"</string>
     <string name="button_default" msgid="3988017840431881491">"Oletusarvot"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Kieli ja syöttötapa"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Valitse syöttötapa"</string>
 </resources>
diff --git a/java/res/values-fr/strings.xml b/java/res/values-fr/strings.xml
index c552a7f..f327f90 100644
--- a/java/res/values-fr/strings.xml
+++ b/java/res/values-fr/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Installer ce fichier pour la langue \"<xliff:g id="LOCALE_NAME">%s</xliff:g>\" ?"</string>
     <string name="error" msgid="8940763624668513648">"Une erreur s\'est produite"</string>
     <string name="button_default" msgid="3988017840431881491">"Par défaut"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Langue et saisie"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Sélectionnez le mode de saisie"</string>
 </resources>
diff --git a/java/res/values-hi/strings.xml b/java/res/values-hi/strings.xml
index 72320ea..e52143d 100644
--- a/java/res/values-hi/strings.xml
+++ b/java/res/values-hi/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> के लिए वास्तव में यह फ़ाइल इंस्टॉल करें?"</string>
     <string name="error" msgid="8940763624668513648">"कोई त्रुटि हुई थी"</string>
     <string name="button_default" msgid="3988017840431881491">"डिफ़ॉल्ट"</string>
+    <string name="language_settings" msgid="1671153053201809031">"भाषा और इनपुट"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"इनपुट पद्धति चुनें"</string>
 </resources>
diff --git a/java/res/values-hr/strings.xml b/java/res/values-hr/strings.xml
index 8432ed5..564d48f 100644
--- a/java/res/values-hr/strings.xml
+++ b/java/res/values-hr/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Želite li doista instalirati ovu datoteku za <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Došlo je do pogreške"</string>
     <string name="button_default" msgid="3988017840431881491">"Zadano"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Jezik i unos"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Odabir načina unosa"</string>
 </resources>
diff --git a/java/res/values-hu/strings.xml b/java/res/values-hu/strings.xml
index eb4bde2..047a31a 100644
--- a/java/res/values-hu/strings.xml
+++ b/java/res/values-hu/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Valóban telepíti ezt a fájlt <xliff:g id="LOCALE_NAME">%s</xliff:g> nyelvhez?"</string>
     <string name="error" msgid="8940763624668513648">"Hiba történt."</string>
     <string name="button_default" msgid="3988017840431881491">"Alapértelmezett"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Nyelv és bevitel"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Beviteli mód kiválasztása"</string>
 </resources>
diff --git a/java/res/values-in/strings.xml b/java/res/values-in/strings.xml
index 646c3aa..ba9a7da 100644
--- a/java/res/values-in/strings.xml
+++ b/java/res/values-in/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Yakin ingin memasang file ini untuk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Terjadi kesalahan"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Bahasa &amp; masukan"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Pilih metode masukan"</string>
 </resources>
diff --git a/java/res/values-is/strings.xml b/java/res/values-is/strings.xml
deleted file mode 100644
index 678d3cf..0000000
--- a/java/res/values-is/strings.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
-    <skip />
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Áfram"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Næsta"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Fyrra"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Lokið"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Senda"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- no translation found for prefs_description_log (5827825607258246003) -->
-    <skip />
-    <!-- no translation found for keyboard_layout (8451164783510487501) -->
-    <skip />
-    <!-- no translation found for subtype_en_GB (88170601942311355) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- no translation found for prefs_key_longpress_timeout_settings (1881822418815012326) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
-    <skip />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-</resources>
diff --git a/java/res/values-it/strings.xml b/java/res/values-it/strings.xml
index e353d8e..56acdf8 100644
--- a/java/res/values-it/strings.xml
+++ b/java/res/values-it/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Installare questo file per <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Si è verificato un errore"</string>
     <string name="button_default" msgid="3988017840431881491">"Predefinito"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Lingua e input"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Scegli il metodo di immissione"</string>
 </resources>
diff --git a/java/res/values-iw/strings.xml b/java/res/values-iw/strings.xml
index e537efb..7c55ab5 100644
--- a/java/res/values-iw/strings.xml
+++ b/java/res/values-iw/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"האם באמת להתקין את הקובץ הזה עבור <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"אירעה שגיאה"</string>
     <string name="button_default" msgid="3988017840431881491">"ברירת מחדל"</string>
+    <string name="language_settings" msgid="1671153053201809031">"שפה וקלט"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"בחירת שיטת קלט"</string>
 </resources>
diff --git a/java/res/values-ja/strings.xml b/java/res/values-ja/strings.xml
index 9aedb6d..9328f01 100644
--- a/java/res/values-ja/strings.xml
+++ b/java/res/values-ja/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"この<xliff:g id="LOCALE_NAME">%s</xliff:g>のファイルをインストールしてもよろしいですか?"</string>
     <string name="error" msgid="8940763624668513648">"エラーが発生しました"</string>
     <string name="button_default" msgid="3988017840431881491">"デフォルト"</string>
+    <string name="language_settings" msgid="1671153053201809031">"言語と入力"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"入力方法の選択"</string>
 </resources>
diff --git a/java/res/values-ka/strings.xml b/java/res/values-ka/strings.xml
deleted file mode 100644
index 4c0f17c..0000000
--- a/java/res/values-ka/strings.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
-    <skip />
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"გადასვლა"</string>
-    <string name="label_next_key" msgid="362972844525672568">"შემდეგი"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"წინა"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"შესრულებულია"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"გაგზავნა"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- no translation found for prefs_description_log (5827825607258246003) -->
-    <skip />
-    <!-- no translation found for keyboard_layout (8451164783510487501) -->
-    <skip />
-    <!-- no translation found for subtype_en_GB (88170601942311355) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- no translation found for prefs_key_longpress_timeout_settings (1881822418815012326) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
-    <skip />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-</resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index 13fb4cf..f6086ee 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"이 파일을 <xliff:g id="LOCALE_NAME">%s</xliff:g>(으)로 설치하시겠습니까?"</string>
     <string name="error" msgid="8940763624668513648">"오류 발생"</string>
     <string name="button_default" msgid="3988017840431881491">"기본값"</string>
+    <string name="language_settings" msgid="1671153053201809031">"언어 및 키보드"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"입력 방법 선택"</string>
 </resources>
diff --git a/java/res/values-lt/strings.xml b/java/res/values-lt/strings.xml
index b023a17..a815003 100644
--- a/java/res/values-lt/strings.xml
+++ b/java/res/values-lt/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Ar tikrai įdiegti šį failą <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Įvyko klaida"</string>
     <string name="button_default" msgid="3988017840431881491">"Numatytieji"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Kalba ir įvestis"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Pasirinkite įvesties metodą"</string>
 </resources>
diff --git a/java/res/values-lv/strings.xml b/java/res/values-lv/strings.xml
index 23be520..6a408cf 100644
--- a/java/res/values-lv/strings.xml
+++ b/java/res/values-lv/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vai instalēt šo failu šādai valodai: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Radās kļūda"</string>
     <string name="button_default" msgid="3988017840431881491">"Noklusējums"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Valoda un ievade"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Ievades metodes izvēle"</string>
 </resources>
diff --git a/java/res/values-mk/strings.xml b/java/res/values-mk/strings.xml
deleted file mode 100644
index e7189d5..0000000
--- a/java/res/values-mk/strings.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
-    <skip />
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Оди"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Следно"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Претходно"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Готово"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Испрати"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- no translation found for prefs_description_log (5827825607258246003) -->
-    <skip />
-    <!-- no translation found for keyboard_layout (8451164783510487501) -->
-    <skip />
-    <!-- no translation found for subtype_en_GB (88170601942311355) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- no translation found for prefs_key_longpress_timeout_settings (1881822418815012326) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
-    <skip />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-</resources>
diff --git a/java/res/values-mn/strings.xml b/java/res/values-mn/strings.xml
deleted file mode 100644
index 0372701..0000000
--- a/java/res/values-mn/strings.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-/*
-**
-** Copyright 2008, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
- -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for aosp_android_keyboard_ime_name (7877134937939182296) -->
-    <skip />
-    <!-- no translation found for english_ime_input_options (3909945612939668554) -->
-    <skip />
-    <!-- no translation found for english_ime_research_log (8492602295696577851) -->
-    <skip />
-    <!-- no translation found for aosp_spell_checker_service_name (6985142605330377819) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_title (5374120998125353898) -->
-    <skip />
-    <!-- no translation found for use_contacts_for_spellchecking_option_summary (8754413382543307713) -->
-    <skip />
-    <!-- no translation found for vibrate_on_keypress (5258079494276955460) -->
-    <skip />
-    <!-- no translation found for sound_on_keypress (6093592297198243644) -->
-    <skip />
-    <!-- no translation found for popup_on_keypress (123894815723512944) -->
-    <skip />
-    <!-- no translation found for general_category (1859088467017573195) -->
-    <skip />
-    <!-- no translation found for correction_category (2236750915056607613) -->
-    <skip />
-    <!-- no translation found for gesture_typing_category (497263612130532630) -->
-    <skip />
-    <!-- no translation found for misc_category (6894192814868233453) -->
-    <skip />
-    <!-- no translation found for advanced_settings (362895144495591463) -->
-    <skip />
-    <!-- no translation found for advanced_settings_summary (4487980456152830271) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list (4533689960308565519) -->
-    <skip />
-    <!-- no translation found for include_other_imes_in_language_switch_list_summary (840637129103317635) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key (5915478828318774384) -->
-    <skip />
-    <!-- no translation found for show_language_switch_key_summary (7343403647474265713) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview (6604262359510068370) -->
-    <skip />
-    <!-- no translation found for sliding_key_input_preview_summary (6340524345729093886) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_delay (6213164897443068248) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_no_delay (2096123151571458064) -->
-    <skip />
-    <!-- no translation found for key_preview_popup_dismiss_default_delay (2166964333903906734) -->
-    <skip />
-    <!-- no translation found for abbreviation_unit_milliseconds (8700286094028323363) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict (4435317977804180815) -->
-    <skip />
-    <!-- no translation found for use_contacts_dict_summary (6599983334507879959) -->
-    <skip />
-    <!-- no translation found for use_double_space_period (8781529969425082860) -->
-    <skip />
-    <!-- no translation found for use_double_space_period_summary (6532892187247952799) -->
-    <skip />
-    <!-- no translation found for auto_cap (1719746674854628252) -->
-    <skip />
-    <!-- no translation found for auto_cap_summary (7934452761022946874) -->
-    <skip />
-    <!-- no translation found for configure_dictionaries_title (4238652338556902049) -->
-    <skip />
-    <!-- no translation found for main_dictionary (4798763781818361168) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions (8026799663445531637) -->
-    <skip />
-    <!-- no translation found for prefs_show_suggestions_summary (1583132279498502825) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_name (3219916594067551303) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_show_only_portrait_name (3859783767435239118) -->
-    <skip />
-    <!-- no translation found for prefs_suggestion_visibility_hide_name (6309143926422234673) -->
-    <skip />
-    <!-- no translation found for auto_correction (7630720885194996950) -->
-    <skip />
-    <!-- no translation found for auto_correction_summary (5625751551134658006) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_off (8470882665417944026) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_modest (8788366690620799097) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_aggeressive (3524029103734923819) -->
-    <skip />
-    <!-- no translation found for auto_correction_threshold_mode_very_aggeressive (3386782235540547678) -->
-    <skip />
-    <!-- no translation found for bigram_prediction (1084449187723948550) -->
-    <skip />
-    <!-- no translation found for bigram_prediction_summary (3896362682751109677) -->
-    <skip />
-    <!-- no translation found for gesture_input (826951152254563827) -->
-    <skip />
-    <!-- no translation found for gesture_input_summary (9180350639305731231) -->
-    <skip />
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (4443240334739381053) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (4472696213996203533) -->
-    <skip />
-    <!-- no translation found for added_word (8993883354622484372) -->
-    <skip />
-    <string name="label_go_key" msgid="1635148082137219148">"Очих"</string>
-    <string name="label_next_key" msgid="362972844525672568">"Дараагийн"</string>
-    <string name="label_previous_key" msgid="1211868118071386787">"Өмнөх"</string>
-    <string name="label_done_key" msgid="2441578748772529288">"Хийгдлээ"</string>
-    <string name="label_send_key" msgid="2815056534433717444">"Илгээх"</string>
-    <!-- no translation found for label_pause_key (181098308428035340) -->
-    <skip />
-    <!-- no translation found for label_wait_key (6402152600878093134) -->
-    <skip />
-    <!-- no translation found for spoken_use_headphones (896961781287283493) -->
-    <skip />
-    <!-- no translation found for spoken_current_text_is (2485723011272583845) -->
-    <skip />
-    <!-- no translation found for spoken_no_text_entered (7479685225597344496) -->
-    <skip />
-    <!-- no translation found for spoken_description_unknown (3197434010402179157) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift (244197883292549308) -->
-    <skip />
-    <!-- no translation found for spoken_description_shift_shifted (1681877323344195035) -->
-    <skip />
-    <!-- no translation found for spoken_description_caps_lock (3276478269526304432) -->
-    <skip />
-    <!-- no translation found for spoken_description_delete (8740376944276199801) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_symbol (5486340107500448969) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_alpha (23129338819771807) -->
-    <skip />
-    <!-- no translation found for spoken_description_to_numeric (591752092685161732) -->
-    <skip />
-    <!-- no translation found for spoken_description_settings (4627462689603838099) -->
-    <skip />
-    <!-- no translation found for spoken_description_tab (2667716002663482248) -->
-    <skip />
-    <!-- no translation found for spoken_description_space (2582521050049860859) -->
-    <skip />
-    <!-- no translation found for spoken_description_mic (615536748882611950) -->
-    <skip />
-    <!-- no translation found for spoken_description_smiley (2256309826200113918) -->
-    <skip />
-    <!-- no translation found for spoken_description_return (8178083177238315647) -->
-    <skip />
-    <!-- no translation found for spoken_description_search (1247236163755920808) -->
-    <skip />
-    <!-- no translation found for spoken_description_dot (40711082435231673) -->
-    <skip />
-    <!-- no translation found for spoken_description_language_switch (5507091328222331316) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_next (8636078276664150324) -->
-    <skip />
-    <!-- no translation found for spoken_description_action_previous (800872415009336208) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_on (5700440798609574589) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_locked (593175803181701830) -->
-    <skip />
-    <!-- no translation found for spoken_description_shiftmode_off (657219998449174808) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_symbol (7183343879909747642) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_alpha (3528307674390156956) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone (6520207943132026264) -->
-    <skip />
-    <!-- no translation found for spoken_description_mode_phone_shift (5499629753962641227) -->
-    <skip />
-    <!-- no translation found for voice_input (3583258583521397548) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_main_keyboard (3360660341121083174) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_symbols_keyboard (7203213240786084067) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_off (3745699748218082014) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_main_keyboard (6586544292900314339) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_symbols_keyboard (5233725927281932391) -->
-    <skip />
-    <!-- no translation found for voice_input_modes_summary_off (63875609591897607) -->
-    <skip />
-    <!-- no translation found for configure_input_method (373356270290742459) -->
-    <skip />
-    <!-- no translation found for language_selection_title (1651299598555326750) -->
-    <skip />
-    <!-- no translation found for select_language (3693815588777926848) -->
-    <skip />
-    <!-- no translation found for hint_add_to_dictionary (573678656946085380) -->
-    <skip />
-    <!-- no translation found for has_dictionary (6071847973466625007) -->
-    <skip />
-    <!-- no translation found for prefs_enable_log (6620424505072963557) -->
-    <skip />
-    <!-- no translation found for prefs_description_log (5827825607258246003) -->
-    <skip />
-    <!-- no translation found for keyboard_layout (8451164783510487501) -->
-    <skip />
-    <!-- no translation found for subtype_en_GB (88170601942311355) -->
-    <skip />
-    <!-- no translation found for subtype_en_US (6160452336634534239) -->
-    <skip />
-    <!-- no translation found for subtype_es_US (5583145191430180200) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_GB (2179097748724725906) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_en_US (1362581347576714579) -->
-    <skip />
-    <!-- no translation found for subtype_with_layout_es_US (6261791057007890189) -->
-    <skip />
-    <!-- no translation found for subtype_no_language (141420857808801746) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwerty (2956121451616633133) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_qwertz (1177848172397202890) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_azerty (8721460968141187394) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_dvorak (3122976737669823935) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_colemak (4205992994906097244) -->
-    <skip />
-    <!-- no translation found for subtype_no_language_pcqwerty (8840928374394180189) -->
-    <skip />
-    <!-- no translation found for custom_input_styles_title (8429952441821251512) -->
-    <skip />
-    <!-- no translation found for add_style (6163126614514489951) -->
-    <skip />
-    <!-- no translation found for add (8299699805688017798) -->
-    <skip />
-    <!-- no translation found for remove (4486081658752944606) -->
-    <skip />
-    <!-- no translation found for save (7646738597196767214) -->
-    <skip />
-    <!-- no translation found for subtype_locale (8576443440738143764) -->
-    <skip />
-    <!-- no translation found for keyboard_layout_set (4309233698194565609) -->
-    <skip />
-    <!-- no translation found for custom_input_style_note_message (8826731320846363423) -->
-    <skip />
-    <!-- no translation found for enable (5031294444630523247) -->
-    <skip />
-    <!-- no translation found for not_now (6172462888202790482) -->
-    <skip />
-    <!-- no translation found for custom_input_style_already_exists (8008728952215449707) -->
-    <skip />
-    <!-- no translation found for prefs_usability_study_mode (1261130555134595254) -->
-    <skip />
-    <!-- no translation found for prefs_key_longpress_timeout_settings (1881822418815012326) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_vibration_duration_settings (1829950405285211668) -->
-    <skip />
-    <!-- no translation found for prefs_keypress_sound_volume_settings (5875933757082305040) -->
-    <skip />
-    <!-- no translation found for prefs_read_external_dictionary (2588931418575013067) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_no_files_message (4947420942224623792) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_multiple_files_title (7637749044265808628) -->
-    <skip />
-    <!-- no translation found for read_external_dictionary_confirm_install_message (6898610163768980870) -->
-    <skip />
-    <!-- no translation found for error (8940763624668513648) -->
-    <skip />
-    <!-- no translation found for button_default (3988017840431881491) -->
-    <skip />
-</resources>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 37f923c..0d4ccf4 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Betul-betul pasang fail ini untuk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Berlaku ralat"</string>
     <string name="button_default" msgid="3988017840431881491">"Lalai"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Bahasa &amp; input"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Pilih kaedah input"</string>
 </resources>
diff --git a/java/res/values-nb/strings.xml b/java/res/values-nb/strings.xml
index b79933f..f0b3a46 100644
--- a/java/res/values-nb/strings.xml
+++ b/java/res/values-nb/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vil du virkelig installere denne filen for <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Det oppsto en feil"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Språk og inndata"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Velg inndatametode"</string>
 </resources>
diff --git a/java/res/values-nl/strings.xml b/java/res/values-nl/strings.xml
index 3dba983..dc3a9c3 100644
--- a/java/res/values-nl/strings.xml
+++ b/java/res/values-nl/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Wilt u dit bestand voor <xliff:g id="LOCALE_NAME">%s</xliff:g> echt installeren?"</string>
     <string name="error" msgid="8940763624668513648">"Er is een fout opgetreden"</string>
     <string name="button_default" msgid="3988017840431881491">"Standaard"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Taal en invoer"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Invoermethode selecteren"</string>
 </resources>
diff --git a/java/res/values-pl/strings.xml b/java/res/values-pl/strings.xml
index 11602b8..de100bc 100644
--- a/java/res/values-pl/strings.xml
+++ b/java/res/values-pl/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Czy na pewno zainstalować ten plik dla języka: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Wystąpił błąd"</string>
     <string name="button_default" msgid="3988017840431881491">"Domyślne"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Język, klawiatura, głos"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Wybierz metodę wprowadzania"</string>
 </resources>
diff --git a/java/res/values-pt-rPT/strings.xml b/java/res/values-pt-rPT/strings.xml
index 6e65015..68a3457 100644
--- a/java/res/values-pt-rPT/strings.xml
+++ b/java/res/values-pt-rPT/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Instalar mesmo este ficheiro para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ocorreu um erro"</string>
     <string name="button_default" msgid="3988017840431881491">"Predefinido"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Idioma e entrada de som"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Escolher o método de entrada"</string>
 </resources>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index 33f5eca..00ab88c 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Deseja instalar este arquivo para <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ocorreu um erro"</string>
     <string name="button_default" msgid="3988017840431881491">"Padrão"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Idioma e entrada"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Selecione o método de entrada"</string>
 </resources>
diff --git a/java/res/values-rm/strings.xml b/java/res/values-rm/strings.xml
index bbef69d..9eec7f8 100644
--- a/java/res/values-rm/strings.xml
+++ b/java/res/values-rm/strings.xml
@@ -281,4 +281,8 @@
     <skip />
     <!-- no translation found for button_default (3988017840431881491) -->
     <skip />
+    <!-- no translation found for language_settings (1671153053201809031) -->
+    <skip />
+    <!-- no translation found for select_input_method (4301602374609275003) -->
+    <skip />
 </resources>
diff --git a/java/res/values-ro/strings.xml b/java/res/values-ro/strings.xml
index 5c6ff3f..c5a80e3 100644
--- a/java/res/values-ro/strings.xml
+++ b/java/res/values-ro/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Doriți să instalați acest fișier pentru <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"A apărut o eroare"</string>
     <string name="button_default" msgid="3988017840431881491">"Prestabilit"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Limbă și introducere de text"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Alegeți metoda de introducere de text"</string>
 </resources>
diff --git a/java/res/values-ru/strings.xml b/java/res/values-ru/strings.xml
index 5e746bb..13a5678 100644
--- a/java/res/values-ru/strings.xml
+++ b/java/res/values-ru/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Установить этот файл для следующего языка: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ошибка"</string>
     <string name="button_default" msgid="3988017840431881491">"По умолчанию"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Язык и ввод"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Выберите способ ввода"</string>
 </resources>
diff --git a/java/res/values-sk/strings.xml b/java/res/values-sk/strings.xml
index 9fd9798..0b28bf1 100644
--- a/java/res/values-sk/strings.xml
+++ b/java/res/values-sk/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Chcete nainštalovať tento súbor pre jazyk <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Vyskytla sa chyba"</string>
     <string name="button_default" msgid="3988017840431881491">"Predvolené"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Jazyk &amp; vstup"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Zvoliť metódu vstupu"</string>
 </resources>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index fa441d7..50f3869 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Zares želite namestiti to datoteko za jezik <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Prišlo je do napake"</string>
     <string name="button_default" msgid="3988017840431881491">"Privzeto"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Jezik in vnos"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Izbira načina vnosa"</string>
 </resources>
diff --git a/java/res/values-sr/strings.xml b/java/res/values-sr/strings.xml
index 0a97437..2d13db0 100644
--- a/java/res/values-sr/strings.xml
+++ b/java/res/values-sr/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Желите ли стварно да инсталирате ову датотеку за <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Дошло је до грешке"</string>
     <string name="button_default" msgid="3988017840431881491">"Подразумевано"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Језик и унос"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Избор метода уноса"</string>
 </resources>
diff --git a/java/res/values-sv/strings.xml b/java/res/values-sv/strings.xml
index dd8af36..608caad 100644
--- a/java/res/values-sv/strings.xml
+++ b/java/res/values-sv/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Vill du verkligen installera filen för <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Ett fel uppstod"</string>
     <string name="button_default" msgid="3988017840431881491">"Standard"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Språk &amp; inmatning"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Välj inmatningsmetod"</string>
 </resources>
diff --git a/java/res/values-sw/strings.xml b/java/res/values-sw/strings.xml
index e8a32bf..104a5d4 100644
--- a/java/res/values-sw/strings.xml
+++ b/java/res/values-sw/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Isakinishe faili hii kwa <xliff:g id="LOCALE_NAME">%s</xliff:g> kweli?"</string>
     <string name="error" msgid="8940763624668513648">"Kulikuwa na hitilafu"</string>
     <string name="button_default" msgid="3988017840431881491">"Chaguo-msingi"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Lugha na uingizaji"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Chagua mbinu ya kuingiza data"</string>
 </resources>
diff --git a/java/res/values-sw600dp-land/setup-dimens.xml b/java/res/values-sw600dp-land/setup-dimens.xml
new file mode 100644
index 0000000..9aea214
--- /dev/null
+++ b/java/res/values-sw600dp-land/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">64sp</dimen>
+    <dimen name="setup_horizontal_padding">96dp</dimen>
+</resources>
diff --git a/java/res/values-sw768dp-land/setup-dimens.xml b/java/res/values-sw768dp-land/setup-dimens.xml
new file mode 100644
index 0000000..0d2af17
--- /dev/null
+++ b/java/res/values-sw768dp-land/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">64sp</dimen>
+    <dimen name="setup_horizontal_padding">192dp</dimen>
+</resources>
diff --git a/java/res/values-th/strings.xml b/java/res/values-th/strings.xml
index 008e3e2..1db5e91 100644
--- a/java/res/values-th/strings.xml
+++ b/java/res/values-th/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"ติดตั้งไฟล์นี้สำหรับ <xliff:g id="LOCALE_NAME">%s</xliff:g> จริงๆ หรือ"</string>
     <string name="error" msgid="8940763624668513648">"เกิดข้อผิดพลาด"</string>
     <string name="button_default" msgid="3988017840431881491">"ค่าเริ่มต้น"</string>
+    <string name="language_settings" msgid="1671153053201809031">"ภาษาและการป้อนข้อมูล"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"เลือกวิธีการป้อนข้อมูล"</string>
 </resources>
diff --git a/java/res/values-tl/strings.xml b/java/res/values-tl/strings.xml
index ae29e6a..adf0a19 100644
--- a/java/res/values-tl/strings.xml
+++ b/java/res/values-tl/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"I-install talaga ang file na ito para sa <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Nagkaroon ng error"</string>
     <string name="button_default" msgid="3988017840431881491">"Default"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Wika at input"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Pumili ng pamamaraan ng pag-input"</string>
 </resources>
diff --git a/java/res/values-tr/strings.xml b/java/res/values-tr/strings.xml
index eb8f2dc..fd80fea 100644
--- a/java/res/values-tr/strings.xml
+++ b/java/res/values-tr/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"<xliff:g id="LOCALE_NAME">%s</xliff:g> için bu dosya gerçekten yüklensin mi?"</string>
     <string name="error" msgid="8940763624668513648">"Bir hata oluştu"</string>
     <string name="button_default" msgid="3988017840431881491">"Varsayılan"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Dil ve giriş"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Giriş yöntemini seçin"</string>
 </resources>
diff --git a/java/res/values-uk/strings.xml b/java/res/values-uk/strings.xml
index 5eaad63..2e2fdfc 100644
--- a/java/res/values-uk/strings.xml
+++ b/java/res/values-uk/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Справді встановити цей файл для такої мови: <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Сталася помилка"</string>
     <string name="button_default" msgid="3988017840431881491">"За умовчанням"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Мова та введення"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Вибрати метод введення"</string>
 </resources>
diff --git a/java/res/values-vi/strings.xml b/java/res/values-vi/strings.xml
index fd778d5..6721980 100644
--- a/java/res/values-vi/strings.xml
+++ b/java/res/values-vi/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Thực sự cài đặt tệp này cho <xliff:g id="LOCALE_NAME">%s</xliff:g>?"</string>
     <string name="error" msgid="8940763624668513648">"Đã xảy ra lỗi"</string>
     <string name="button_default" msgid="3988017840431881491">"Mặc định"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Ngôn ngữ và phương thức nhập"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Chọn phương thức nhập"</string>
 </resources>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index bc94938..ebb6b91 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"确定要为<xliff:g id="LOCALE_NAME">%s</xliff:g>安装此文件吗?"</string>
     <string name="error" msgid="8940763624668513648">"出现错误"</string>
     <string name="button_default" msgid="3988017840431881491">"默认"</string>
+    <string name="language_settings" msgid="1671153053201809031">"语言和输入法"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"选择输入法"</string>
 </resources>
diff --git a/java/res/values-zh-rTW/strings.xml b/java/res/values-zh-rTW/strings.xml
index 0bbd30b..7d4ea72 100644
--- a/java/res/values-zh-rTW/strings.xml
+++ b/java/res/values-zh-rTW/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"準備為<xliff:g id="LOCALE_NAME">%s</xliff:g>版本安裝這個檔案嗎?"</string>
     <string name="error" msgid="8940763624668513648">"發生錯誤"</string>
     <string name="button_default" msgid="3988017840431881491">"預設"</string>
+    <string name="language_settings" msgid="1671153053201809031">"語言與輸入設定"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"選擇輸入法"</string>
 </resources>
diff --git a/java/res/values-zu/strings.xml b/java/res/values-zu/strings.xml
index 5848b95..29ed595 100644
--- a/java/res/values-zu/strings.xml
+++ b/java/res/values-zu/strings.xml
@@ -157,4 +157,6 @@
     <string name="read_external_dictionary_confirm_install_message" msgid="6898610163768980870">"Ufuna ukufakela i-<xliff:g id="LOCALE_NAME">%s</xliff:g> leli fayela ngokweqiniso?"</string>
     <string name="error" msgid="8940763624668513648">"Kube nephutha"</string>
     <string name="button_default" msgid="3988017840431881491">"Okuzenzakalelayo"</string>
+    <string name="language_settings" msgid="1671153053201809031">"Ulimi nokokufakwayo"</string>
+    <string name="select_input_method" msgid="4301602374609275003">"Khetha indlela yokufaka"</string>
 </resources>
diff --git a/java/res/values/colors.xml b/java/res/values/colors.xml
index c0ea321..8a8049f 100644
--- a/java/res/values/colors.xml
+++ b/java/res/values/colors.xml
@@ -53,4 +53,9 @@
     <color name="spacebar_text_color_ics">#FFC0C0C0</color>
     <color name="spacebar_text_shadow_color_ics">#80000000</color>
     <color name="typed_word_color_ics">@color/highlight_color_ics</color>
+    <!-- Color resources for setup wizard and tutorial -->
+    <color name="setup_background">#FFEBEBEB</color>
+    <color name="setup_text_dark">#FF707070</color>
+    <color name="setup_text_action">@android:color/holo_blue_light</color>
+    <color name="setup_step_background">@android:color/background_light</color>
 </resources>
diff --git a/java/res/values/setup-dimens.xml b/java/res/values/setup-dimens.xml
new file mode 100644
index 0000000..007906d
--- /dev/null
+++ b/java/res/values/setup-dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <dimen name="setup_title_text_size">46sp</dimen>
+    <dimen name="setup_horizontal_padding">16dp</dimen>
+</resources>
diff --git a/java/res/values/setup-styles.xml b/java/res/values/setup-styles.xml
new file mode 100644
index 0000000..cfc689a
--- /dev/null
+++ b/java/res/values/setup-styles.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <style name="setupTitleStyle">
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">@dimen/setup_title_text_size</item>
+    </style>
+    <style name="setupStepBulletStyle">
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">22sp</item>
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_weight">1.0</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:gravity">center_horizontal</item>
+    </style>
+    <style name="setupStepTitleStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">22sp</item>
+    </style>
+    <style name="setupStepInstructionStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_dark</item>
+        <item name="android:textSize">14sp</item>
+    </style>
+    <style name="setupStepActionLabelStyle">
+        <item name="android:background">@color/setup_step_background</item>
+        <item name="android:textColor">@color/setup_text_action</item>
+        <item name="android:textSize">18sp</item>
+    </style>
+</resources>
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index ee79b45..e89174b 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -223,6 +223,29 @@
     <!-- Spoken feedback after changing to the shifted phone dialer (symbols) keyboard. -->
     <string name="spoken_description_mode_phone_shift">Phone symbols mode</string>
 
+    <!-- Spoken feedback when the keyboard is hidden. -->
+    <string name="announce_keyboard_hidden">Keyboard hidden</string>
+    <!-- Spoken feedback when the keyboard mode changes. -->
+    <string name="announce_keyboard_mode">Showing <xliff:g id="mode" example="email">%s</xliff:g> keyboard</string>
+    <!-- Description of the keyboard mode for entering dates. -->
+    <string name="keyboard_mode_date">date</string>
+    <!-- Description of the keyboard mode for entering dates and times. -->
+    <string name="keyboard_mode_date_time">date and time</string>
+    <!-- Description of the keyboard mode for entering email addresses. -->
+    <string name="keyboard_mode_email">email</string>
+    <!-- Description of the keyboard mode for entering text messages. -->
+    <string name="keyboard_mode_im">messaging</string>
+    <!-- Description of the keyboard mode for entering numbers. -->
+    <string name="keyboard_mode_number">number</string>
+    <!-- Description of the keyboard mode for entering phone numbers. -->
+    <string name="keyboard_mode_phone">phone</string>
+    <!-- Description of the keyboard mode for entering generic text. -->
+    <string name="keyboard_mode_text">text</string>
+    <!-- Description of the keyboard mode for entering times. -->
+    <string name="keyboard_mode_time">time</string>
+    <!-- Description of the keyboard mode for entering URLs. -->
+    <string name="keyboard_mode_url">URL</string>
+
     <!-- Preferences item for enabling speech input -->
     <string name="voice_input">Voice input key</string>
 
@@ -408,4 +431,32 @@
 
     <!-- Title of the button to revert to the default value of the device in the settings dialog [CHAR LIMIT=15] -->
     <string name="button_default">Default</string>
+
+    <!-- TODO: Remove translatable="false" once wordings are finalized. -->
+    <!-- Title of the setup wizard. [CHAR LIMT=40] -->
+    <string name="setup_title" translatable="false">"Installing <xliff:g id="application_name">%s</xliff:g>"</string>
+    <!-- Ordinal number of the 1st step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step1_bullet" translatable="false">1</string>
+    <!-- Title of the 1st step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step1_title" translatable="false">"Enable <xliff:g id="application_name">%s</xliff:g> in settings."</string>
+    <!-- Detailed instruction of the 1st step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step1_instruction" translatable="false">"For security, please check \"<xliff:g id="application_name">%s</xliff:g>\""</string>
+    <!-- Ordinal number of the 2nd step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step2_bullet" translatable="false">2</string>
+    <!-- Title of the 2nd step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step2_title" translatable="false">"Switch to <xliff:g id="application_name">%s</xliff:g>."</string>
+    <!-- Detailed instruction of the 2nd step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step2_instruction" translatable="false">"Now that you've enabled <xliff:g id="application_name">%s</xliff:g>, you can switch to it."</string>
+    <!-- Ordinal number of the 3rd step in the setup wizard. [CHAR LIMIT=5] -->
+    <string name="setup_step3_bullet" translatable="false">3</string>
+    <!-- Title of the 3rd step in the setup wizard. [CHAR LIMIT=64] -->
+    <string name="setup_step3_title" translatable="false">"Congratulations, you're all set!"</string>
+    <!-- Detailed instruction of the 3rd step in the setup wizard. [CHAR LIMIT=80] -->
+    <string name="setup_step3_instruction" translatable="false">Configure additional languages</string>
+    <!-- Title of the Language & input settings. This should be aligned with msgid="5292716747264442359" -->
+    <string name="language_settings">Language &amp; input</string>
+    <!-- Title of the Input method picker. This should be aligned with msgid="4653387336791222978" -->
+    <string name="select_input_method">Choose input method</string>
+    <!-- Option to show setup wizard icon. [CHAR LIMIT=30]-->
+    <string name="show_setup_wizard_icon" translatable="false">Show setup wizard icon</string>
 </resources>
diff --git a/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml b/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
index a0e5cbb..0e3013a 100644
--- a/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
+++ b/java/res/xml-sw600dp/keys_pcqwerty3_right2.xml
@@ -28,19 +28,9 @@
             <Key
                 latin:keyLabel=";"
                 latin:moreKeys=":" />
-            <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-                 U+2019: "’" RIGHT SINGLE QUOTATION MARK
-                 U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-                 U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-                 U+201C: "“" LEFT DOUBLE QUOTATION MARK
-                 U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-                 U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-                 U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-            <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-                 moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x2018;,&#x2019;,&#x201A;,&#x201B;" -->
             <Key
                 latin:keyLabel="\'"
-                latin:moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x2018;,&#x2019;,&#x201A;,&#x201B;,&quot;" />
+                latin:moreKeys="!fixedColumnOrder!3,!text/double_quotes,!text/single_quotes" />
         </case>
         <default>
             <Key
@@ -48,20 +38,10 @@
                 latin:keyHintLabel=":"
                 latin:moreKeys=":"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
-            <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-                 U+2019: "’" RIGHT SINGLE QUOTATION MARK
-                 U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-                 U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-                 U+201C: "“" LEFT DOUBLE QUOTATION MARK
-                 U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-                 U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-                 U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-            <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-                 moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x2018;,&#x2019;,&#x201A;,&#x201B;" -->
             <Key
                 latin:keyLabel="\'"
                 latin:keyHintLabel="&quot;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x2018;,&#x2019;,&#x201A;,&#x201B;,&quot;"
+                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,&quot;,!text/single_quotes"
                 latin:keyStyle="hasShiftedLetterHintStyle" />
         </default>
     </switch>
diff --git a/java/res/xml-v16/key_hindi1_shift.xml b/java/res/xml-v16/key_hindi1_shift.xml
new file mode 100644
index 0000000..19b9643
--- /dev/null
+++ b/java/res/xml-v16/key_hindi1_shift.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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+    <Key
+        latin:keyLabel="&#x0903;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_hindi3_right.xml b/java/res/xml-v16/key_hindi3_right.xml
new file mode 100644
index 0000000..232810f
--- /dev/null
+++ b/java/res/xml-v16/key_hindi3_right.xml
@@ -0,0 +1,36 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+093C: "़" DEVANAGARI SIGN NUKTA
+         U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
+         U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
+         U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+    <Key
+        latin:keyLabel="&#x093C;"
+        latin:moreKeys="&#x097D;,&#x0970;,&#x093D;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_hindi3_shift_left.xml b/java/res/xml-v16/key_hindi3_shift_left.xml
new file mode 100644
index 0000000..1eb1768
--- /dev/null
+++ b/java/res/xml-v16/key_hindi3_shift_left.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
+         U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
+    <Key
+        latin:keyLabel="&#x0901;"
+        latin:moreKeys="&#x0945;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/key_hindi3_shift_right.xml b/java/res/xml-v16/key_hindi3_shift_right.xml
new file mode 100644
index 0000000..0f26cb5
--- /dev/null
+++ b/java/res/xml-v16/key_hindi3_shift_right.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
+         U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
+    <Key
+        latin:keyLabel="&#x0943;"
+        latin:moreKeys="&#x0944;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keys_hindi1_left5.xml b/java/res/xml-v16/keys_hindi1_left5.xml
new file mode 100644
index 0000000..e3ad299
--- /dev/null
+++ b/java/res/xml-v16/keys_hindi1_left5.xml
@@ -0,0 +1,75 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
+         U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
+         U+0967: "१" DEVANAGARI DIGIT ONE -->
+    <Key
+        latin:keyLabel="&#x094C;"
+        latin:moreKeys="&#x094C;&#x0902;,%"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="&#x0967;,1"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI
+         U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
+         U+0968: "२" DEVANAGARI DIGIT TWO -->
+    <Key
+        latin:keyLabel="&#x0948;"
+        latin:moreKeys="&#x0948;&#x0902;,%"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="&#x0968;,2"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA
+         U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
+         U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
+         U+0969: "३" DEVANAGARI DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x093E;"
+        latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="&#x0969;,3"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
+         U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
+         U+096A: "४" DEVANAGARI DIGIT FOUR -->
+    <Key
+        latin:keyLabel="&#x0940;"
+        latin:moreKeys="&#x0940;&#x0902;,%"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="&#x096A;,4"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU
+         U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
+         U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
+         U+096B: "५" DEVANAGARI DIGIT FIVE -->
+    <Key
+        latin:keyLabel="&#x0942;"
+        latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="&#x096B;,5"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keys_hindi2_left5.xml b/java/res/xml-v16/keys_hindi2_left5.xml
new file mode 100644
index 0000000..05c4f57
--- /dev/null
+++ b/java/res/xml-v16/keys_hindi2_left5.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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O
+         U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
+         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
+         U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
+    <Key
+        latin:keyLabel="&#x094B;"
+        latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E
+         U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x0947;"
+        latin:moreKeys="&#x0947;&#x0902;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
+    <Key
+        latin:keyLabel="&#x094D;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I
+         U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x093F;"
+        latin:moreKeys="&#x093F;&#x0902;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U
+         U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
+         U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
+    <Key
+        latin:keyLabel="&#x0941;"
+        latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml-v16/keys_hindi3_left2.xml b/java/res/xml-v16/keys_hindi3_left2.xml
new file mode 100644
index 0000000..9474c17
--- /dev/null
+++ b/java/res/xml-v16/keys_hindi3_left2.xml
@@ -0,0 +1,36 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
+    <Key
+        latin:keyLabel="&#x0949;"
+        latin:keyLabelFlags="fontNormal" />
+    <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x0902;"
+        latin:keyLabelFlags="fontNormal" />
+</merge>
diff --git a/java/res/xml/key_hindi1_shift.xml b/java/res/xml/key_hindi1_shift.xml
new file mode 100644
index 0000000..0db5ae9
--- /dev/null
+++ b/java/res/xml/key_hindi1_shift.xml
@@ -0,0 +1,34 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0903: "ः" DEVANAGARI SIGN VISARGA -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0903;"
+        latin:code="0x0903"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_hindi3_right.xml b/java/res/xml/key_hindi3_right.xml
new file mode 100644
index 0000000..5a97355
--- /dev/null
+++ b/java/res/xml/key_hindi3_right.xml
@@ -0,0 +1,38 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093C: "़" DEVANAGARI SIGN NUKTA
+         U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
+         U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
+         U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x093C;"
+        latin:code="0x093C"
+        latin:moreKeys="&#x25CC;&#x097D;|&#x097D;,&#x25CC;&#x0970;|&#x0970;,&#x25CC;&#x093D;|&#x093D;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_hindi3_shift_left.xml b/java/res/xml/key_hindi3_shift_left.xml
new file mode 100644
index 0000000..c5e2f13
--- /dev/null
+++ b/java/res/xml/key_hindi3_shift_left.xml
@@ -0,0 +1,36 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
+         U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0901;"
+        latin:code="0x0901"
+        latin:moreKeys="&#x25CC;&#x0945;|&#x0945;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_hindi3_shift_right.xml b/java/res/xml/key_hindi3_shift_right.xml
new file mode 100644
index 0000000..0da116a
--- /dev/null
+++ b/java/res/xml/key_hindi3_shift_right.xml
@@ -0,0 +1,36 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
+         U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0943;"
+        latin:code="0x0943"
+        latin:moreKeys="&#x25CC;&#x0944;|&#x0944;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/key_styles_enter.xml b/java/res/xml/key_styles_enter.xml
index 61a515b..5976e95 100644
--- a/java/res/xml/key_styles_enter.xml
+++ b/java/res/xml/key_styles_enter.xml
@@ -104,7 +104,7 @@
         latin:parentStyle="defaultEnterKeyStyle" />
     <key-style
         latin:styleName="defaultActionEnterKeyStyle"
-        latin:code="!code/key_action_enter"
+        latin:code="!code/key_enter"
         latin:keyIcon="!icon/undefined"
         latin:backgroundType="action"
         latin:parentStyle="defaultEnterKeyStyle" />
diff --git a/java/res/xml/keys_hindi1_left5.xml b/java/res/xml/keys_hindi1_left5.xml
new file mode 100644
index 0000000..8757afe
--- /dev/null
+++ b/java/res/xml/keys_hindi1_left5.xml
@@ -0,0 +1,85 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
+         U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
+         U+0967: "१" DEVANAGARI DIGIT ONE -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x094C;"
+        latin:code="0x094C"
+        latin:moreKeys="&#x25CC;&#x094C;&#x0902;|&#x094C;&#x0902;,%"
+        latin:keyHintLabel="1"
+        latin:additionalMoreKeys="&#x0967;,1"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0948: "ै" DEVANAGARI VOWEL SIGN AI
+         U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
+         U+0968: "२" DEVANAGARI DIGIT TWO -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0948;"
+        latin:code="0x0948"
+        latin:moreKeys="&#x25CC;&#x0948;&#x0902;|&#x0948;&#x0902;,%"
+        latin:keyHintLabel="2"
+        latin:additionalMoreKeys="&#x0968;,2"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093E: "ा" DEVANAGARI VOWEL SIGN AA
+         U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
+         U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
+         U+0969: "३" DEVANAGARI DIGIT THREE -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x093E;"
+        latin:code="0x093E"
+        latin:moreKeys="&#x25CC;&#x093E;&#x0902;|&#x093E;&#x0902;,&#x25CC;&#x093E;&#x0901;|&#x093E;&#x0901;,%"
+        latin:keyHintLabel="3"
+        latin:additionalMoreKeys="&#x0969;,3"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0940: "ी" DEVANAGARI VOWEL SIGN II
+         U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
+         U+096A: "४" DEVANAGARI DIGIT FOUR -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0940;"
+        latin:code="0x0940"
+        latin:moreKeys="&#x25CC;&#x0940;&#x0902;|&#x0940;&#x0902;,%"
+        latin:keyHintLabel="4"
+        latin:additionalMoreKeys="&#x096A;,4"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0942: "ू" DEVANAGARI VOWEL SIGN UU
+         U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
+         U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
+         U+096B: "५" DEVANAGARI DIGIT FIVE -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0942;"
+        latin:code="0x0942"
+        latin:moreKeys="&#x25CC;&#x0942;&#x0902;|&#x0942;&#x0902;,&#x25CC;&#x0942;&#x0901;|&#x0942;&#x0901;,%"
+        latin:keyHintLabel="5"
+        latin:additionalMoreKeys="&#x096B;,5"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keys_hindi2_left5.xml b/java/res/xml/keys_hindi2_left5.xml
new file mode 100644
index 0000000..4c3a5e0
--- /dev/null
+++ b/java/res/xml/keys_hindi2_left5.xml
@@ -0,0 +1,69 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+094B: "ो" DEVANAGARI VOWEL SIGN O
+         U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
+         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
+         U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x094B;"
+        latin:code="0x094B"
+        latin:moreKeys="&#x25CC;&#x094B;&#x0902;|&#x094B;&#x0902;,&#x25CC;&#x0949;,&#x094A;|&#x0949;,&#x094A;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0947: "े" DEVANAGARI VOWEL SIGN E
+         U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0947;"
+        latin:code="0x0947"
+        latin:moreKeys="&#x25CC;&#x0947;&#x0902;|&#x0947;&#x0902;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+094D: "्" DEVANAGARI SIGN VIRAMA -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x094D;"
+        latin:code="0x094D"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+093F: "ि" DEVANAGARI VOWEL SIGN I
+         U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x093F;&#x25CC;"
+        latin:code="0x093F"
+        latin:moreKeys="&#x093F;&#x25CC;&#x0902;|&#x093F;&#x0902;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0941: "ु" DEVANAGARI VOWEL SIGN U
+         U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
+         U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0941;"
+        latin:code="0x0941"
+        latin:moreKeys="&#x25CC;&#x0941;&#x0902;|&#x0941;&#x0902;,&#x25CC;&#x0941;&#x0901;|&#x0941;&#x0901;"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keys_hindi3_left2.xml b/java/res/xml/keys_hindi3_left2.xml
new file mode 100644
index 0000000..4f1ad16
--- /dev/null
+++ b/java/res/xml/keys_hindi3_left2.xml
@@ -0,0 +1,40 @@
+<?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.
+*/
+-->
+
+<!-- The code point U+25CC for key label is needed because the font rendering system prior to
+     API version 16 can't automatically render dotted circle for incomplete combining letter
+     of Hindi. The files named res/xml/{key,keys}_hindi*.xml have this U+25CC hack, although the
+     counterpart files named res/xml-v16/{key,keys}_hindi*.xml don't have this hack. -->
+<merge
+    xmlns:latin="http://schemas.android.com/apk/res/com.android.inputmethod.latin"
+>
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0949;"
+        latin:code="0x0949"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+    <!-- U+25CC: "◌" DOTTED CIRCLE
+         U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
+    <Key
+        latin:keyLabel="&#x25CC;&#x0902;"
+        latin:code="0x0902"
+        latin:keyLabelFlags="fontNormal|followKeyLetterRatio" />
+</merge>
diff --git a/java/res/xml/keys_pcqwerty3_right2.xml b/java/res/xml/keys_pcqwerty3_right2.xml
index fd9d3b8..aa150af 100644
--- a/java/res/xml/keys_pcqwerty3_right2.xml
+++ b/java/res/xml/keys_pcqwerty3_right2.xml
@@ -30,25 +30,15 @@
                 latin:moreKeys=":" />
             <Key
                 latin:keyLabel="\'"
-                latin:moreKeys="&quot;" />
+                latin:moreKeys="!fixedColumnOrder!4,!text/double_quotes,&quot;,!text/single_quotes" />
         </case>
         <!-- keyboardLayoutSetElement="alphabet*Shifted|symbols*" -->
         <default>
             <Key
                 latin:keyLabel=":" />
-            <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-                 U+2019: "’" RIGHT SINGLE QUOTATION MARK
-                 U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-                 U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-                 U+201C: "“" LEFT DOUBLE QUOTATION MARK
-                 U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-                 U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-                 U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-            <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-                 moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x2018;,&#x2019;,&#x201A;,&#x201B;" -->
             <Key
                 latin:keyLabel="&quot;"
-                latin:moreKeys="!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x2018;,&#x2019;,&#x201A;,&#x201B;" />
+                latin:moreKeys="!fixedColumnOrder!3,!text/double_quotes,!text/single_quotes" />
         </default>
     </switch>
 </merge>
diff --git a/java/res/xml/prefs.xml b/java/res/xml/prefs.xml
index 5ed0bb0..e5fef88 100644
--- a/java/res/xml/prefs.xml
+++ b/java/res/xml/prefs.xml
@@ -60,7 +60,11 @@
            <intent
               android:action="android.intent.action.MAIN"
               android:targetPackage="@string/dictionary_pack_package_name"
-              android:targetClass="@string/dictionary_pack_settings_activity" />
+              android:targetClass="@string/dictionary_pack_settings_activity">
+             <extra
+                 android:name="clientId"
+                 android:value="@string/dictionary_pack_client_id" />
+           </intent>
         </PreferenceScreen>
         <ListPreference
             android:key="auto_correction_threshold"
@@ -141,7 +145,7 @@
                 android:fragment="com.android.inputmethod.latin.AdditionalSubtypeSettings"
                 android:key="custom_input_styles"
                 android:title="@string/custom_input_styles_title" />
-            <!-- Values for popup dismiss delay are added programatically -->
+            <!-- Values for popup dismiss delay are added programmatically -->
             <CheckBoxPreference
                 android:key="pref_sliding_key_input_preview"
                 android:title="@string/sliding_key_input_preview"
@@ -167,6 +171,11 @@
                 android:key="pref_keypress_sound_volume"
                 android:title="@string/prefs_keypress_sound_volume_settings"
                 latin:maxValue="100" /> <!-- percent -->
+            <!-- The show setup wizard icon settings shouldn't be persistent and the default value
+                 is added programmatically. -->
+            <CheckBoxPreference
+                android:key="pref_show_setup_wizard_icon"
+                android:title="@string/show_setup_wizard_icon" />
         </PreferenceScreen>
         <PreferenceScreen
             android:key="debug_settings"
diff --git a/java/res/xml/row_symbols_shift4.xml b/java/res/xml/row_symbols_shift4.xml
index 1bfb5ec..99a685c 100644
--- a/java/res/xml/row_symbols_shift4.xml
+++ b/java/res/xml/row_symbols_shift4.xml
@@ -27,13 +27,11 @@
         <Key
             latin:keyStyle="toAlphaKeyStyle"
             latin:keyWidth="15%p" />
-        <!-- U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-        <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-        <!-- latin:keyLabelFlags="hasPopupHint" -->
-        <!-- latin:moreKeys="&#x201F;" -->
-        <!-- U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
+        <!-- U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+             U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
         <Key
             latin:keyLabel="&#x201E;"
+            latin:moreKeys="&#x201A;"
             latin:backgroundType="functional" />
         <include
             latin:keyXPos="25%p"
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
index 1120804..a761a6c 100644
--- a/java/res/xml/rowkeys_hindi1.xml
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -61,10 +61,11 @@
             <Key
                 latin:keyLabel="&#x092D;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0903: "ः" DEVANAGARI SIGN VISARGA -->
-            <Key
-                latin:keyLabel="&#x0903;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_hindi1_shift" />
             <!-- U+0918: "घ" DEVANAGARI LETTER GHA -->
             <Key
                 latin:keyLabel="&#x0918;"
@@ -86,53 +87,11 @@
                 latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
-            <!-- U+094C: "ौ" DEVANAGARI VOWEL SIGN AU
-                 U+094C/U+0902: "ौं" DEVANAGARI VOWEL SIGN AU/DEVANAGARI SIGN ANUSVARA
-                 U+0967: "१" DEVANAGARI DIGIT ONE -->
-            <Key
-                latin:keyLabel="&#x094C;"
-                latin:moreKeys="&#x094C;&#x0902;,%"
-                latin:keyHintLabel="1"
-                latin:additionalMoreKeys="&#x0967;,1"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0948: "ै" DEVANAGARI VOWEL SIGN AI
-                 U+0948/U+0902: "ैं" DEVANAGARI VOWEL SIGN AI/DEVANAGARI SIGN ANUSVARA
-                 U+0968: "२" DEVANAGARI DIGIT TWO -->
-            <Key
-                latin:keyLabel="&#x0948;"
-                latin:moreKeys="&#x0948;&#x0902;,%"
-                latin:keyHintLabel="2"
-                latin:additionalMoreKeys="&#x0968;,2"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+093E: "ा" DEVANAGARI VOWEL SIGN AA
-                 U+093E/U+0902: "ां" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN ANUSVARA
-                 U+093E/U+0901: "ाँ" DEVANAGARI VOWEL SIGN AA/DEVANAGARI SIGN CANDRABINDU
-                 U+0969: "३" DEVANAGARI DIGIT THREE -->
-            <Key
-                latin:keyLabel="&#x093E;"
-                latin:moreKeys="&#x093E;&#x0902;,&#x093E;&#x0901;,%"
-                latin:keyHintLabel="3"
-                latin:additionalMoreKeys="&#x0969;,3"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0940: "ी" DEVANAGARI VOWEL SIGN II
-                 U+0940/U+0902: "ीं" DEVANAGARI VOWEL SIGN II/DEVANAGARI SIGN ANUSVARA
-                 U+096A: "४" DEVANAGARI DIGIT FOUR -->
-            <Key
-                latin:keyLabel="&#x0940;"
-                latin:moreKeys="&#x0940;&#x0902;,%"
-                latin:keyHintLabel="4"
-                latin:additionalMoreKeys="&#x096A;,4"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0942: "ू" DEVANAGARI VOWEL SIGN UU
-                 U+0942/U+0902: "ूं" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN ANUSVARA
-                 U+0942/U+0901: "ूँ" DEVANAGARI VOWEL SIGN UU/DEVANAGARI SIGN CANDRABINDU
-                 U+096B: "५" DEVANAGARI DIGIT FIVE -->
-            <Key
-                latin:keyLabel="&#x0942;"
-                latin:moreKeys="&#x0942;&#x0902;,&#x0942;&#x0901;,%"
-                latin:keyHintLabel="5"
-                latin:additionalMoreKeys="&#x096B;,5"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keys_hindi1_left5" />
             <!-- U+092C: "ब" DEVANAGARI LETTER BA
                  U+092C/U+0952: "ब॒" DEVANAGARI LETTER BA/DEVANAGARI STRESS SIGN ANUDATTA -->
             <Key
diff --git a/java/res/xml/rowkeys_hindi2.xml b/java/res/xml/rowkeys_hindi2.xml
index e7c67db..9545b84 100644
--- a/java/res/xml/rowkeys_hindi2.xml
+++ b/java/res/xml/rowkeys_hindi2.xml
@@ -96,37 +96,11 @@
                 latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
-            <!-- U+094B: "ो" DEVANAGARI VOWEL SIGN O
-                 U+094B/U+0902: "қं" DEVANAGARI VOWEL SIGN O/DEVANAGARI SIGN ANUSVARA
-                 U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O
-                 U+094A: "ॊ" DEVANAGARI VOWEL SIGN SHORT O -->
-            <Key
-                latin:keyLabel="&#x094B;"
-                latin:moreKeys="&#x094B;&#x0902;,&#x0949;,&#x094A;"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0947: "े" DEVANAGARI VOWEL SIGN E
-                 U+0947/U+0902: "ें" DEVANAGARI VOWEL SIGN E/DEVANAGARI SIGN ANUSVARA -->
-            <Key
-                latin:keyLabel="&#x0947;"
-                latin:moreKeys="&#x0947;&#x0902;"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+094D: "्" DEVANAGARI SIGN VIRAMA -->
-            <Key
-                latin:keyLabel="&#x094D;"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+093F: "ि" DEVANAGARI VOWEL SIGN I
-                 U+093F/U+0902: "िं" DEVANAGARI VOWEL SIGN I/DEVANAGARI SIGN ANUSVARA -->
-            <Key
-                latin:keyLabel="&#x093F;"
-                latin:moreKeys="&#x093F;&#x0902;"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0941: "ु" DEVANAGARI VOWEL SIGN U
-                 U+0941/U+0902: "ुं" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN ANUSVARA
-                 U+0941/U+0901: "ुँ" DEVANAGARI VOWEL SIGN U/DEVANAGARI SIGN CANDRABINDU -->
-            <Key
-                latin:keyLabel="&#x0941;"
-                latin:moreKeys="&#x0941;&#x0902;,&#x0941;&#x0901;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keys_hindi2_left5" />
             <!-- U+092A: "प" DEVANAGARI LETTER PA -->
             <Key
                 latin:keyLabel="&#x092A;"
diff --git a/java/res/xml/rowkeys_hindi3.xml b/java/res/xml/rowkeys_hindi3.xml
index ebbff3e..3014907 100644
--- a/java/res/xml/rowkeys_hindi3.xml
+++ b/java/res/xml/rowkeys_hindi3.xml
@@ -29,12 +29,11 @@
             <Key
                 latin:keyLabel="&#x0911;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0901: "ँ" DEVANAGARI SIGN CANDRABINDU
-                 U+0945: "ॅ" DEVANAGARI VOWEL SIGN CANDRA E-->
-            <Key
-                latin:keyLabel="&#x0901;"
-                latin:moreKeys="&#x0945;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_hindi3_shift_left" />
             <!-- U+0923: "ण" DEVANAGARI LETTER NNA -->
             <Key
                 latin:keyLabel="&#x0923;"
@@ -56,26 +55,22 @@
             <Key
                 latin:keyLabel="&#x0937;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+0943: "ृ" DEVANAGARI VOWEL SIGN VOCALIC R
-                 U+0944: "ॄ" DEVANAGARI VOWEL SIGN VOCALIC RR -->
-            <Key
-                latin:keyLabel="&#x0943;"
-                latin:moreKeys="&#x0944;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_hindi3_shift_right" />
             <!-- U+091E: "ञ" DEVANAGARI LETTER NYA -->
             <Key
                 latin:keyLabel="&#x091E;"
                 latin:keyLabelFlags="fontNormal" />
         </case>
         <default>
-            <!-- U+0949: "ॉ" DEVANAGARI VOWEL SIGN CANDRA O -->
-            <Key
-                latin:keyLabel="&#x0949;"
-                latin:keyLabelFlags="fontNormal" />
-            <!-- U+0902: "ं" DEVANAGARI SIGN ANUSVARA -->
-            <Key
-                latin:keyLabel="&#x0902;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/keys_hindi3_left2" />
             <!-- U+092E: "म" DEVANAGARI LETTER MA
                  U+0950: "ॐ" DEVANAGARI OM -->
             <Key
@@ -111,14 +106,11 @@
                 latin:keyLabel="&#x092F;"
                 latin:moreKeys="&#x095F;"
                 latin:keyLabelFlags="fontNormal" />
-            <!-- U+093C: "़" DEVANAGARI SIGN NUKTA
-                 U+097D: "ॽ" DEVANAGARI LETTER GLOTTAL STOP
-                 U+0970: "॰" DEVANAGARI ABBREVIATION SIGN
-                 U+093D: "ऽ" DEVANAGARI SIGN AVAGRAHA -->
-            <Key
-                latin:keyLabel="&#x093C;"
-                latin:moreKeys="&#x097D;,&#x0970;,&#x093D;"
-                latin:keyLabelFlags="fontNormal" />
+            <!-- Because the font rendering system prior to API version 16 can't automatically
+                 render dotted circle for incomplete combining letter of Hindi, different set of
+                 Key definitions are needed based on the API version. -->
+            <include
+                latin:keyboardLayout="@xml/key_hindi3_right" />
          </default>
     </switch>
 </merge>
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
index bf1cea9..ee52de1 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java
@@ -80,16 +80,24 @@
     }
 
     /**
+     * Returns {@code true} if accessibility is enabled. Currently, this means
+     * that the kill switch is off and system accessibility is turned on.
+     *
+     * @return {@code true} if accessibility is enabled.
+     */
+    public boolean isAccessibilityEnabled() {
+        return ENABLE_ACCESSIBILITY && mAccessibilityManager.isEnabled();
+    }
+
+    /**
      * Returns {@code true} if touch exploration is enabled. Currently, this
      * means that the kill switch is off, the device supports touch exploration,
-     * and a spoken feedback service is turned on.
+     * and system accessibility is turned on.
      *
      * @return {@code true} if touch exploration is enabled.
      */
     public boolean isTouchExplorationEnabled() {
-        return ENABLE_ACCESSIBILITY
-                && mAccessibilityManager.isEnabled()
-                && mAccessibilityManager.isTouchExplorationEnabled();
+        return isAccessibilityEnabled() && mAccessibilityManager.isTouchExplorationEnabled();
     }
 
     /**
@@ -113,6 +121,7 @@
      *
      * @return {@code true} if the device should obscure password characters.
      */
+    @SuppressWarnings("deprecation")
     public boolean shouldObscureInput(final EditorInfo editorInfo) {
         if (editorInfo == null) return false;
 
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
index d05fd9e..e6b4412 100644
--- a/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
+++ b/java/src/com/android/inputmethod/accessibility/AccessibleKeyboardViewProxy.java
@@ -22,8 +22,11 @@
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewParent;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
@@ -35,6 +38,21 @@
 public final class AccessibleKeyboardViewProxy extends AccessibilityDelegateCompat {
     private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
 
+    /** Map of keyboard modes to resource IDs. */
+    private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray();
+
+    static {
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATE, R.string.keyboard_mode_date);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_DATETIME, R.string.keyboard_mode_date_time);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_EMAIL, R.string.keyboard_mode_email);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_IM, R.string.keyboard_mode_im);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_NUMBER, R.string.keyboard_mode_number);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_PHONE, R.string.keyboard_mode_phone);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TEXT, R.string.keyboard_mode_text);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_TIME, R.string.keyboard_mode_time);
+        KEYBOARD_MODE_RES_IDS.put(KeyboardId.MODE_URL, R.string.keyboard_mode_url);
+    }
+
     private InputMethodService mInputMethod;
     private MainKeyboardView mView;
     private AccessibilityEntityProvider mAccessibilityNodeProvider;
@@ -85,11 +103,75 @@
         mAccessibilityNodeProvider.setView(view);
     }
 
+    /**
+     * Called when the keyboard layout changes.
+     * <p>
+     * <b>Note:</b> This method will be called even if accessibility is not
+     * enabled.
+     */
     public void setKeyboard() {
-        if (mAccessibilityNodeProvider == null) {
+        if (mAccessibilityNodeProvider != null) {
+            mAccessibilityNodeProvider.setKeyboard();
+        }
+
+        // Since this method is called even when accessibility is off, make sure
+        // to check the state before announcing anything.
+        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            announceKeyboardMode();
+        }
+    }
+
+    /**
+     * Called when the keyboard is hidden and accessibility is enabled.
+     */
+    public void onHideWindow() {
+        announceKeyboardHidden();
+    }
+
+    /**
+     * Announces which type of keyboard is being displayed. If the keyboard type
+     * is unknown, no announcement is made.
+     */
+    private void announceKeyboardMode() {
+        final Keyboard keyboard = mView.getKeyboard();
+        final int resId = KEYBOARD_MODE_RES_IDS.get(keyboard.mId.mMode);
+        if (resId == 0) {
             return;
         }
-        mAccessibilityNodeProvider.setKeyboard();
+
+        final Context context = mView.getContext();
+        final String keyboardMode = context.getString(resId);
+        final String text = context.getString(R.string.announce_keyboard_mode, keyboardMode);
+
+        sendWindowStateChanged(text);
+    }
+
+    /**
+     * Announces that the keyboard has been hidden.
+     */
+    private void announceKeyboardHidden() {
+        final Context context = mView.getContext();
+        final String text = context.getString(R.string.announce_keyboard_hidden);
+
+        sendWindowStateChanged(text);
+    }
+
+    /**
+     * Sends a window state change event with the specified text.
+     *
+     * @param text
+     */
+    private void sendWindowStateChanged(final String text) {
+        final AccessibilityEvent stateChange = AccessibilityEvent.obtain(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        mView.onInitializeAccessibilityEvent(stateChange);
+        stateChange.getText().add(text);
+        stateChange.setContentDescription(null);
+
+        final ViewParent parent = mView.getParent();
+        if (parent != null) {
+            parent.requestSendAccessibilityEvent(mView, stateChange);
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
index ea86d98..05d8269 100644
--- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
+++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java
@@ -110,7 +110,9 @@
             return getDescriptionForShiftKey(context, keyboard);
         }
 
-        if (code == Constants.CODE_ACTION_ENTER) {
+        if (code == Constants.CODE_ENTER) {
+            // The following function returns the correct description in all action and
+            // regular enter cases, taking care of all modes.
             return getDescriptionForActionKey(context, keyboard, key);
         }
 
diff --git a/java/src/com/android/inputmethod/compat/CompatUtils.java b/java/src/com/android/inputmethod/compat/CompatUtils.java
index 5a2b6bd..660029b 100644
--- a/java/src/com/android/inputmethod/compat/CompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/CompatUtils.java
@@ -81,7 +81,7 @@
         try {
             return constructor.newInstance(args);
         } catch (Exception e) {
-            Log.e(TAG, "Exception in newInstance: " + e.getClass().getSimpleName());
+            Log.e(TAG, "Exception in newInstance", e);
         }
         return null;
     }
@@ -92,7 +92,7 @@
         try {
             return method.invoke(receiver, args);
         } catch (Exception e) {
-            Log.e(TAG, "Exception in invoke: " + e.getClass().getSimpleName());
+            Log.e(TAG, "Exception in invoke", e);
         }
         return defaultValue;
     }
@@ -103,7 +103,7 @@
         try {
             return field.get(receiver);
         } catch (Exception e) {
-            Log.e(TAG, "Exception in getFieldValue: " + e.getClass().getSimpleName());
+            Log.e(TAG, "Exception in getFieldValue", e);
         }
         return defaultValue;
     }
@@ -113,7 +113,7 @@
         try {
             field.set(receiver, value);
         } catch (Exception e) {
-            Log.e(TAG, "Exception in setFieldValue: " + e.getClass().getSimpleName());
+            Log.e(TAG, "Exception in setFieldValue", e);
         }
     }
 }
diff --git a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
new file mode 100644
index 0000000..df2e22f
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
@@ -0,0 +1,36 @@
+/*
+ * 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.compat;
+
+import android.content.Intent;
+
+public final class IntentCompatUtils {
+    // Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17
+    // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    public static final String ACTION_USER_INITIALIZE =
+            (String)CompatUtils.getFieldValue(null, null,
+                    CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE"));
+
+    private IntentCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static boolean has_ACTION_USER_INITIALIZE(final Intent intent) {
+        return ACTION_USER_INITIALIZE != null && intent != null
+                && ACTION_USER_INITIALIZE.equals(intent.getAction());
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java b/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java
new file mode 100644
index 0000000..d4f1ea8
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/TextViewCompatUtils.java
@@ -0,0 +1,44 @@
+/*
+ * 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.compat;
+
+import android.graphics.drawable.Drawable;
+import android.widget.TextView;
+
+import java.lang.reflect.Method;
+
+public final class TextViewCompatUtils {
+    // Note that TextView.setCompoundDrawablesRelative(Drawable,Drawable,Drawable,Drawable) has
+    // been introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    private static final Method METHOD_setCompoundDrawablesRelative = CompatUtils.getMethod(
+            TextView.class, "setCompoundDrawablesRelative",
+            Drawable.class, Drawable.class, Drawable.class, Drawable.class);
+
+    private TextViewCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static void setCompoundDrawablesRelative(final TextView textView, final Drawable start,
+            final Drawable top, final Drawable end, final Drawable bottom) {
+        if (METHOD_setCompoundDrawablesRelative == null) {
+            textView.setCompoundDrawables(start, top, end, bottom);
+            return;
+        }
+        CompatUtils.invoke(textView, null, METHOD_setCompoundDrawablesRelative,
+                start, top, end, bottom);
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
new file mode 100644
index 0000000..a8fab88
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java
@@ -0,0 +1,68 @@
+/*
+ * 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.compat;
+
+import android.view.View;
+
+import java.lang.reflect.Method;
+
+public final class ViewCompatUtils {
+    // Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in
+    // API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    public static final int LAYOUT_DIRECTION_LTR = (Integer)CompatUtils.getFieldValue(null, 0x0,
+            CompatUtils.getField(View.class, "LAYOUT_DIRECTION_LTR"));
+    public static final int LAYOUT_DIRECTION_RTL = (Integer)CompatUtils.getFieldValue(null, 0x1,
+            CompatUtils.getField(View.class, "LAYOUT_DIRECTION_RTL"));
+
+    // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int), and
+    // View.getLayoutDirection() have been introduced in API level 17
+    // (Build.VERSION_CODE.JELLY_BEAN_MR1).
+    private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod(
+            View.class, "getPaddingEnd");
+    private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod(
+            View.class, "setPaddingRelative",
+            Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE);
+    private static final Method METHOD_getLayoutDirection = CompatUtils.getMethod(
+            View.class, "getLayoutDirection");
+
+    private ViewCompatUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    public static int getPaddingEnd(final View view) {
+        if (METHOD_getPaddingEnd == null) {
+            return view.getPaddingRight();
+        }
+        return (Integer)CompatUtils.invoke(view, 0, METHOD_getPaddingEnd);
+    }
+
+    public static void setPaddingRelative(final View view, final int start, final int top,
+            final int end, final int bottom) {
+        if (METHOD_setPaddingRelative == null) {
+            view.setPadding(start, top, end, bottom);
+            return;
+        }
+        CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom);
+    }
+
+    public static int getLayoutDirection(final View view) {
+        if (METHOD_getLayoutDirection == null) {
+            return LAYOUT_DIRECTION_LTR;
+        }
+        return (Integer)CompatUtils.invoke(view, 0, METHOD_getLayoutDirection);
+    }
+}
diff --git a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
index a2463c2..720d074 100644
--- a/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
+++ b/java/src/com/android/inputmethod/event/HardwareKeyboardEventDecoder.java
@@ -58,11 +58,11 @@
             }
             if (KeyEvent.KEYCODE_ENTER == keyCode) {
                 // The Enter key. If the Shift key is not being pressed, this should send a
-                // CODE_ACTION_ENTER to trigger the action if any, or a carriage return
-                // otherwise. If the Shift key is depressed, this should send a
-                // CODE_SHIFT_ENTER and let Latin IME decide what to do with it.
+                // CODE_ENTER to trigger the action if any, or a carriage return otherwise. If the
+                // Shift key is being pressed, this should send a CODE_SHIFT_ENTER and let
+                // Latin IME decide what to do with it.
                 return Event.createCommittableEvent(keyEvent.isShiftPressed()
-                        ? Constants.CODE_SHIFT_ENTER : Constants.CODE_ACTION_ENTER,
+                        ? Constants.CODE_SHIFT_ENTER : Constants.CODE_ENTER,
                         null /* next */);
             }
             // If not Enter, then we have a committable character. This should be committed
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java
index 1e5af51..d160038 100644
--- a/java/src/com/android/inputmethod/keyboard/Key.java
+++ b/java/src/com/android/inputmethod/keyboard/Key.java
@@ -519,11 +519,11 @@
         // TODO: Handle "bold" here too?
         if ((mLabelFlags & LABEL_FLAGS_FONT_NORMAL) != 0) {
             return Typeface.DEFAULT;
-        } else if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
-            return Typeface.MONOSPACE;
-        } else {
-            return params.mTypeface;
         }
+        if ((mLabelFlags & LABEL_FLAGS_FONT_MONO_SPACE) != 0) {
+            return Typeface.MONOSPACE;
+        }
+        return params.mTypeface;
     }
 
     public final int selectTextSize(final KeyDrawParams params) {
@@ -550,28 +550,51 @@
     public final int selectHintTextSize(final KeyDrawParams params) {
         if (hasHintLabel()) {
             return params.mHintLabelSize;
-        } else if (hasShiftedLetterHint()) {
-            return params.mShiftedLetterHintSize;
-        } else {
-            return params.mHintLetterSize;
         }
+        if (hasShiftedLetterHint()) {
+            return params.mShiftedLetterHintSize;
+        }
+        return params.mHintLetterSize;
     }
 
     public final int selectHintTextColor(final KeyDrawParams params) {
         if (hasHintLabel()) {
             return params.mHintLabelColor;
-        } else if (hasShiftedLetterHint()) {
+        }
+        if (hasShiftedLetterHint()) {
             return isShiftedLetterActivated() ? params.mShiftedLetterHintActivatedColor
                     : params.mShiftedLetterHintInactivatedColor;
-        } else {
-            return params.mHintLetterColor;
         }
+        return params.mHintLetterColor;
     }
 
     public final int selectMoreKeyTextSize(final KeyDrawParams params) {
         return hasLabelsInMoreKeys() ? params.mLabelSize : params.mLetterSize;
     }
 
+    public final String getPreviewLabel() {
+        return isShiftedLetterActivated() ? mHintLabel : mLabel;
+    }
+
+    private boolean previewHasLetterSize() {
+        return (mLabelFlags & LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO) != 0
+                || StringUtils.codePointCount(getPreviewLabel()) == 1;
+    }
+
+    public final int selectPreviewTextSize(final KeyDrawParams params) {
+        if (previewHasLetterSize()) {
+            return params.mPreviewTextSize;
+        }
+        return params.mLetterSize;
+    }
+
+    public Typeface selectPreviewTypeface(final KeyDrawParams params) {
+        if (previewHasLetterSize()) {
+            return selectTypeface(params);
+        }
+        return Typeface.DEFAULT_BOLD;
+    }
+
     public final boolean isAlignLeft() {
         return (mLabelFlags & LABEL_FLAGS_ALIGN_LEFT) != 0;
     }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 4dab50f..350dc69 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -635,15 +635,9 @@
         invalidate(x, y, x + key.mWidth, y + key.mHeight);
     }
 
-    public void closing() {
-        mInvalidateAllKeys = true;
-        mKeyboard = null;
-    }
-
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        closing();
         freeOffscreenBuffer();
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 4d10f0e..d37b69b 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -811,18 +811,14 @@
             background.setState(KEY_PREVIEW_BACKGROUND_DEFAULT_STATE);
             background.setAlpha(PREVIEW_ALPHA);
         }
-        final String label = key.isShiftedLetterActivated() ? key.mHintLabel : key.mLabel;
+        final String label = key.getPreviewLabel();
         // What we show as preview should match what we show on a key top in onDraw().
         if (label != null) {
             // TODO Should take care of temporaryShiftLabel here.
             previewText.setCompoundDrawables(null, null, null, null);
-            if (StringUtils.codePointCount(label) > 1) {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mLetterSize);
-                previewText.setTypeface(Typeface.DEFAULT_BOLD);
-            } else {
-                previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX, drawParams.mPreviewTextSize);
-                previewText.setTypeface(key.selectTypeface(drawParams));
-            }
+            previewText.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+                    key.selectPreviewTextSize(drawParams));
+            previewText.setTypeface(key.selectPreviewTypeface(drawParams));
             previewText.setText(label);
         } else {
             previewText.setCompoundDrawables(null, null, null,
@@ -1236,13 +1232,11 @@
         mDrawingHandler.cancelAllMessages();
     }
 
-    @Override
     public void closing() {
         dismissAllKeyPreviews();
         cancelAllMessages();
         onDismissMoreKeysPanel();
         mMoreKeysKeyboardCache.clear();
-        super.closing();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
index 9e75f8b..0d42ab2 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java
@@ -174,7 +174,6 @@
 
     @Override
     public boolean dismissMoreKeysPanel() {
-        super.closing();
         if (mController == null) return false;
         return mController.onDismissMoreKeysPanel();
     }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
index 0ec6b01..3e25c3b 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java
@@ -48,7 +48,6 @@
         "key_delete",
         "key_settings",
         "key_shortcut",
-        "key_action_enter",
         "key_action_next",
         "key_action_previous",
         "key_shift_enter",
@@ -85,7 +84,6 @@
         Constants.CODE_DELETE,
         Constants.CODE_SETTINGS,
         Constants.CODE_SHORTCUT,
-        Constants.CODE_ACTION_ENTER,
         Constants.CODE_ACTION_NEXT,
         Constants.CODE_ACTION_PREVIOUS,
         Constants.CODE_SHIFT_ENTER,
@@ -118,7 +116,6 @@
         DEFAULT[12],
         DEFAULT[13],
         DEFAULT[14],
-        DEFAULT[15],
         CODE_RIGHT_PARENTHESIS,
         CODE_LEFT_PARENTHESIS,
         CODE_GREATER_THAN_SIGN,
@@ -142,7 +139,7 @@
     };
 
     static {
-        if (DEFAULT.length != RTL.length) {
+        if (DEFAULT.length != RTL.length || DEFAULT.length != ID_TO_NAME.length) {
             throw new RuntimeException("Internal inconsistency");
         }
         for (int i = 0; i < ID_TO_NAME.length; i++) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
index 493093e..d0b382e 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java
@@ -145,94 +145,110 @@
         /* 40 */ "more_keys_for_cyrillic_ie",
         /* 41 */ "more_keys_for_cyrillic_i",
         /* 42 */ "label_to_alpha_key",
-        /* 43 */ "more_keys_for_single_quote",
-        /* 44 */ "more_keys_for_double_quote",
-        /* 45 */ "more_keys_for_tablet_double_quote",
-        /* 46 */ "more_keys_for_currency_dollar",
-        /* 47 */ "keylabel_for_currency_generic",
-        /* 48 */ "more_keys_for_currency_generic",
-        /* 49 */ "more_keys_for_punctuation",
-        /* 50 */ "more_keys_for_star",
-        /* 51 */ "more_keys_for_bullet",
-        /* 52 */ "more_keys_for_plus",
-        /* 53 */ "more_keys_for_left_parenthesis",
-        /* 54 */ "more_keys_for_right_parenthesis",
-        /* 55 */ "more_keys_for_less_than",
-        /* 56 */ "more_keys_for_greater_than",
-        /* 57 */ "more_keys_for_arabic_diacritics",
-        /* 58 */ "keyhintlabel_for_arabic_diacritics",
-        /* 59 */ "keylabel_for_symbols_1",
-        /* 60 */ "keylabel_for_symbols_2",
-        /* 61 */ "keylabel_for_symbols_3",
-        /* 62 */ "keylabel_for_symbols_4",
-        /* 63 */ "keylabel_for_symbols_5",
-        /* 64 */ "keylabel_for_symbols_6",
-        /* 65 */ "keylabel_for_symbols_7",
-        /* 66 */ "keylabel_for_symbols_8",
-        /* 67 */ "keylabel_for_symbols_9",
-        /* 68 */ "keylabel_for_symbols_0",
-        /* 69 */ "label_to_symbol_key",
-        /* 70 */ "label_to_symbol_with_microphone_key",
-        /* 71 */ "additional_more_keys_for_symbols_1",
-        /* 72 */ "additional_more_keys_for_symbols_2",
-        /* 73 */ "additional_more_keys_for_symbols_3",
-        /* 74 */ "additional_more_keys_for_symbols_4",
-        /* 75 */ "additional_more_keys_for_symbols_5",
-        /* 76 */ "additional_more_keys_for_symbols_6",
-        /* 77 */ "additional_more_keys_for_symbols_7",
-        /* 78 */ "additional_more_keys_for_symbols_8",
-        /* 79 */ "additional_more_keys_for_symbols_9",
-        /* 80 */ "additional_more_keys_for_symbols_0",
-        /* 81 */ "more_keys_for_symbols_1",
-        /* 82 */ "more_keys_for_symbols_2",
-        /* 83 */ "more_keys_for_symbols_3",
-        /* 84 */ "more_keys_for_symbols_4",
-        /* 85 */ "more_keys_for_symbols_5",
-        /* 86 */ "more_keys_for_symbols_6",
-        /* 87 */ "more_keys_for_symbols_7",
-        /* 88 */ "more_keys_for_symbols_8",
-        /* 89 */ "more_keys_for_symbols_9",
-        /* 90 */ "more_keys_for_symbols_0",
-        /* 91 */ "keylabel_for_comma",
-        /* 92 */ "more_keys_for_comma",
-        /* 93 */ "keylabel_for_symbols_question",
-        /* 94 */ "keylabel_for_symbols_semicolon",
-        /* 95 */ "keylabel_for_symbols_percent",
-        /* 96 */ "more_keys_for_symbols_exclamation",
-        /* 97 */ "more_keys_for_symbols_question",
-        /* 98 */ "more_keys_for_symbols_semicolon",
-        /* 99 */ "more_keys_for_symbols_percent",
-        /* 100 */ "keylabel_for_tablet_comma",
-        /* 101 */ "keyhintlabel_for_tablet_comma",
-        /* 102 */ "more_keys_for_tablet_comma",
-        /* 103 */ "keyhintlabel_for_tablet_period",
-        /* 104 */ "more_keys_for_tablet_period",
-        /* 105 */ "keylabel_for_apostrophe",
-        /* 106 */ "keyhintlabel_for_apostrophe",
-        /* 107 */ "more_keys_for_apostrophe",
-        /* 108 */ "more_keys_for_q",
-        /* 109 */ "more_keys_for_x",
-        /* 110 */ "keylabel_for_q",
-        /* 111 */ "keylabel_for_w",
-        /* 112 */ "keylabel_for_y",
-        /* 113 */ "keylabel_for_x",
-        /* 114 */ "keylabel_for_spanish_row2_10",
-        /* 115 */ "more_keys_for_am_pm",
-        /* 116 */ "settings_as_more_key",
-        /* 117 */ "shortcut_as_more_key",
-        /* 118 */ "action_next_as_more_key",
-        /* 119 */ "action_previous_as_more_key",
-        /* 120 */ "label_to_more_symbol_key",
-        /* 121 */ "label_to_more_symbol_for_tablet_key",
-        /* 122 */ "label_tab_key",
-        /* 123 */ "label_to_phone_numeric_key",
-        /* 124 */ "label_to_phone_symbols_key",
-        /* 125 */ "label_time_am",
-        /* 126 */ "label_time_pm",
-        /* 127 */ "label_to_symbol_key_pcqwerty",
-        /* 128 */ "keylabel_for_popular_domain",
-        /* 129 */ "more_keys_for_popular_domain",
-        /* 130 */ "more_keys_for_smiley",
+        /* 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",
     };
 
     private static final String EMPTY = "";
@@ -247,155 +263,182 @@
         /* ~41 */
         // Label for "switch to alphabetic" key.
         /* 42 */ "ABC",
-        /* 43 */ "!fixedColumnOrder!4,\u2018,\u2019,\u201A,\u201B",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;</string>
-        /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B",
+        /* 43 */ "!text/single_lqm_rqm",
+        /* 44 */ "!text/double_lqm_rqm",
+        /* 45 */ "!text/single_laqm_raqm",
+        /* 46 */ "!text/double_laqm_raqm",
         // U+00A2: "¢" CENT SIGN
         // U+00A3: "£" POUND SIGN
         // U+20AC: "€" EURO SIGN
         // U+00A5: "¥" YEN SIGN
         // U+20B1: "₱" PESO SIGN
-        /* 46 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
-        /* 47 */ "$",
-        /* 48 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
-        /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
+        /* 47 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1",
+        /* 48 */ "$",
+        /* 49 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1",
+        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\\,,?,@,&,\\%,+,;,/,(,)",
         // U+2020: "†" DAGGER
         // U+2021: "‡" DOUBLE DAGGER
         // U+2605: "★" BLACK STAR
-        /* 50 */ "\u2020,\u2021,\u2605",
+        /* 51 */ "\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
-        /* 51 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
+        /* 52 */ "\u266A,\u2665,\u2660,\u2666,\u2663",
         // U+00B1: "±" PLUS-MINUS SIGN
-        /* 52 */ "\u00B1",
+        /* 53 */ "\u00B1",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 53 */ "!fixedColumnOrder!3,<,{,[",
-        /* 54 */ "!fixedColumnOrder!3,>,},]",
+        /* 54 */ "!fixedColumnOrder!3,<,{,[",
+        /* 55 */ "!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
-        // The following characters don't need BIDI mirroring.
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        /* 55 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
-        /* 56 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
-        /* 57 */ EMPTY,
+        /* 56 */ "!fixedColumnOrder!3,\u2039,\u2264,\u00AB",
+        /* 57 */ "!fixedColumnOrder!3,\u203A,\u2265,\u00BB",
         /* 58 */ EMPTY,
-        /* 59 */ "1",
-        /* 60 */ "2",
-        /* 61 */ "3",
-        /* 62 */ "4",
-        /* 63 */ "5",
-        /* 64 */ "6",
-        /* 65 */ "7",
-        /* 66 */ "8",
-        /* 67 */ "9",
-        /* 68 */ "0",
+        /* 59 */ EMPTY,
+        /* 60 */ "1",
+        /* 61 */ "2",
+        /* 62 */ "3",
+        /* 63 */ "4",
+        /* 64 */ "5",
+        /* 65 */ "6",
+        /* 66 */ "7",
+        /* 67 */ "8",
+        /* 68 */ "9",
+        /* 69 */ "0",
         // Label for "switch to symbols" key.
-        /* 69 */ "?123",
+        /* 70 */ "?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.
-        /* 70 */ "123",
-        /* 71~ */
+        /* 71 */ "123",
+        /* 72~ */
         EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY,
-        /* ~80 */
+        /* ~81 */
         // 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
-        /* 81 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
+        /* 82 */ "\u00B9,\u00BD,\u2153,\u00BC,\u215B",
         // U+00B2: "²" SUPERSCRIPT TWO
         // U+2154: "⅔" VULGAR FRACTION TWO THIRDS
-        /* 82 */ "\u00B2,\u2154",
+        /* 83 */ "\u00B2,\u2154",
         // U+00B3: "³" SUPERSCRIPT THREE
         // U+00BE: "¾" VULGAR FRACTION THREE QUARTERS
         // U+215C: "⅜" VULGAR FRACTION THREE EIGHTHS
-        /* 83 */ "\u00B3,\u00BE,\u215C",
+        /* 84 */ "\u00B3,\u00BE,\u215C",
         // U+2074: "⁴" SUPERSCRIPT FOUR
-        /* 84 */ "\u2074",
+        /* 85 */ "\u2074",
         // U+215D: "⅝" VULGAR FRACTION FIVE EIGHTHS
-        /* 85 */ "\u215D",
-        /* 86 */ EMPTY,
+        /* 86 */ "\u215D",
+        /* 87 */ EMPTY,
         // U+215E: "⅞" VULGAR FRACTION SEVEN EIGHTHS
-        /* 87 */ "\u215E",
-        /* 88 */ EMPTY,
+        /* 88 */ "\u215E",
         /* 89 */ EMPTY,
+        /* 90 */ EMPTY,
         // U+207F: "ⁿ" SUPERSCRIPT LATIN SMALL LETTER N
         // U+2205: "∅" EMPTY SET
-        /* 90 */ "\u207F,\u2205",
-        /* 91 */ ",",
-        /* 92 */ EMPTY,
-        /* 93 */ "?",
-        /* 94 */ ";",
-        /* 95 */ "%",
+        /* 91 */ "\u207F,\u2205",
+        /* 92 */ ",",
+        /* 93 */ EMPTY,
+        /* 94 */ "?",
+        /* 95 */ ";",
+        /* 96 */ "%",
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 96 */ "\u00A1",
+        /* 97 */ "\u00A1",
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 97 */ "\u00BF",
-        /* 98 */ EMPTY,
+        /* 98 */ "\u00BF",
+        /* 99 */ EMPTY,
         // U+2030: "‰" PER MILLE SIGN
-        /* 99 */ "\u2030",
-        /* 100 */ ",",
-        /* 101 */ "!",
+        /* 100 */ "\u2030",
+        /* 101 */ ",",
         /* 102 */ "!",
-        /* 103 */ "?",
+        /* 103 */ "!",
         /* 104 */ "?",
-        /* 105 */ "\'",
-        /* 106 */ "\"",
+        /* 105 */ "?",
+        /* 106 */ "\'",
         /* 107 */ "\"",
-        /* 108 */ EMPTY,
+        /* 108 */ "\"",
         /* 109 */ EMPTY,
-        /* 110 */ "q",
-        /* 111 */ "w",
-        /* 112 */ "y",
-        /* 113 */ "x",
+        /* 110 */ EMPTY,
+        /* 111 */ "q",
+        /* 112 */ "w",
+        /* 113 */ "y",
+        /* 114 */ "x",
         // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
-        /* 114 */ "\u00F1",
-        /* 115 */ "!fixedColumnOrder!2,!hasLabels!,!text/label_time_am,!text/label_time_pm",
-        /* 116 */ "!icon/settings_key|!code/key_settings",
-        /* 117 */ "!icon/shortcut_key|!code/key_shortcut",
-        /* 118 */ "!hasLabels!,!text/label_next_key|!code/key_action_next",
-        /* 119 */ "!hasLabels!,!text/label_previous_key|!code/key_action_previous",
+        /* 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",
         // Label for "switch to more symbol" modifier key.  Must be short to fit on key!
-        /* 120 */ "= \\ <",
+        /* 121 */ "= \\ <",
         // Label for "switch to more symbol" modifier key on tablets.  Must be short to fit on key!
-        /* 121 */ "~ \\ {",
+        /* 122 */ "~ \\ {",
         // Label for "Tab" key.  Must be short to fit on key!
-        /* 122 */ "Tab",
+        /* 123 */ "Tab",
         // Label for "switch to phone numeric" key.  Must be short to fit on key!
-        /* 123 */ "123",
+        /* 124 */ "123",
         // Label for "switch to phone symbols" key.  Must be short to fit on key!
         // U+FF0A: "*" FULLWIDTH ASTERISK
         // U+FF03: "#" FULLWIDTH NUMBER SIGN
-        /* 124 */ "\uFF0A\uFF03",
+        /* 125 */ "\uFF0A\uFF03",
         // Key label for "ante meridiem"
-        /* 125 */ "AM",
+        /* 126 */ "AM",
         // Key label for "post meridiem"
-        /* 126 */ "PM",
+        /* 127 */ "PM",
         // Label for "switch to symbols" key on PC QWERTY layout
-        /* 127 */ "Sym",
-        /* 128 */ ".com",
+        /* 128 */ "Sym",
+        /* 129 */ ".com",
         // popular web domains for the locale - most popular, displayed on the keyboard
-        /* 129 */ "!hasLabels!,.net,.org,.gov,.edu",
-        /* 130 */ "!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ ",
+        /* 130 */ "!hasLabels!,.net,.org,.gov,.edu",
+        /* 131 */ "!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
+        // U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+        // The following characters don't need BIDI mirroring.
+        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
+        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
+        // Abbreviations are:
+        // laqm: LEFT-POINTING ANGLE QUOTATION MARK
+        // raqm: RIGHT-POINTING ANGLE QUOTATION MARK
+        // rtl: Right-To-Left script order
+        // lqm: LEFT QUOTATION MARK
+        // rqm: RIGHT QUOTATION MARK
+        // 9qm: LOW-9 QUOTATION MARK
+        // 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",
+        // 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",
     };
 
     /* Language af: Afrikaans */
@@ -465,54 +508,36 @@
         // U+062C: "پ" ARABIC LETTER PEH
         /* 42 */ "\u0623\u200C\u0628\u200C\u062C",
         /* 43 */ null,
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
-        /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B",
-        // U+00A2: "¢" CENT SIGN
-        // U+00A3: "£" POUND SIGN
-        // U+20AC: "€" EURO SIGN
-        // U+00A5: "¥" YEN SIGN
-        // U+20B1: "₱" PESO SIGN
-        // U+FDFC: "﷼" RIAL SIGN
-        /* 46 */ "\u00A2,\u00A3,\u20AC,\u00A5,\u20B1,\uFDFC",
-        /* 47 */ null,
-        /* 48 */ null,
+        /* 44 */ null,
+        /* 45 */ "!text/single_laqm_raqm_rtl",
+        /* 46 */ "!text/double_laqm_raqm_rtl",
+        /* 47~ */
+        null, null, null,
+        /* ~49 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 50 */ "\u2605,\u066D",
+        /* 51 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 51 */ "\u266A",
-        /* 52 */ null,
+        /* 52 */ "\u266A",
+        /* 53 */ 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
-        /* 53 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 54 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 55 */ "!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
-        // The following characters don't need BIDI mirroring.
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0654: "ٔ" ARABIC HAMZA ABOVE
         // U+0652: "ْ" ARABIC SUKUN
@@ -529,70 +554,70 @@
         // 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.
-        /* 57 */ "!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",
-        /* 58 */ "\u0651",
+        /* 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",
         // U+0661: "١" ARABIC-INDIC DIGIT ONE
-        /* 59 */ "\u0661",
+        /* 60 */ "\u0661",
         // U+0662: "٢" ARABIC-INDIC DIGIT TWO
-        /* 60 */ "\u0662",
+        /* 61 */ "\u0662",
         // U+0663: "٣" ARABIC-INDIC DIGIT THREE
-        /* 61 */ "\u0663",
+        /* 62 */ "\u0663",
         // U+0664: "٤" ARABIC-INDIC DIGIT FOUR
-        /* 62 */ "\u0664",
+        /* 63 */ "\u0664",
         // U+0665: "٥" ARABIC-INDIC DIGIT FIVE
-        /* 63 */ "\u0665",
+        /* 64 */ "\u0665",
         // U+0666: "٦" ARABIC-INDIC DIGIT SIX
-        /* 64 */ "\u0666",
+        /* 65 */ "\u0666",
         // U+0667: "٧" ARABIC-INDIC DIGIT SEVEN
-        /* 65 */ "\u0667",
+        /* 66 */ "\u0667",
         // U+0668: "٨" ARABIC-INDIC DIGIT EIGHT
-        /* 66 */ "\u0668",
+        /* 67 */ "\u0668",
         // U+0669: "٩" ARABIC-INDIC DIGIT NINE
-        /* 67 */ "\u0669",
+        /* 68 */ "\u0669",
         // U+0660: "٠" ARABIC-INDIC DIGIT ZERO
-        /* 68 */ "\u0660",
+        /* 69 */ "\u0660",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 69 */ "\u0663\u0662\u0661\u061F",
+        /* 70 */ "\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.
-        /* 70 */ "\u0663\u0662\u0661",
-        /* 71 */ "1",
-        /* 72 */ "2",
-        /* 73 */ "3",
-        /* 74 */ "4",
-        /* 75 */ "5",
-        /* 76 */ "6",
-        /* 77 */ "7",
-        /* 78 */ "8",
-        /* 79 */ "9",
+        /* 71 */ "\u0663\u0662\u0661",
+        /* 72 */ "1",
+        /* 73 */ "2",
+        /* 74 */ "3",
+        /* 75 */ "4",
+        /* 76 */ "5",
+        /* 77 */ "6",
+        /* 78 */ "7",
+        /* 79 */ "8",
+        /* 80 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 80 */ "0,\u066B,\u066C",
-        /* 81~ */
+        /* 81 */ "0,\u066B,\u066C",
+        /* 82~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~90 */
+        /* ~91 */
         // U+060C: "،" ARABIC COMMA
-        /* 91 */ "\u060C",
-        /* 92 */ "\\,",
-        /* 93 */ "\u061F",
-        /* 94 */ "\u061B",
+        /* 92 */ "\u060C",
+        /* 93 */ "\\,",
+        /* 94 */ "\u061F",
+        /* 95 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 95 */ "\u066A",
-        /* 96 */ null,
-        /* 97 */ "?",
-        /* 98 */ ";",
+        /* 96 */ "\u066A",
+        /* 97 */ null,
+        /* 98 */ "?",
+        /* 99 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 99 */ "\\%,\u2030",
-        /* 100~ */
+        /* 100 */ "\\%,\u2030",
+        /* 101~ */
         null, null, null, null, null,
-        /* ~104 */
+        /* ~105 */
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 105 */ "\u060C",
-        /* 106 */ "\u061F",
-        /* 107 */ "\u061F,\u061B,!,:,-,/,\',\"",
+        /* 106 */ "\u060C",
+        /* 107 */ "\u061F",
+        /* 108 */ "\u061F,\u061B,!,:,-,/,\',\"",
     };
 
     /* Language be: Belarusian */
@@ -627,6 +652,8 @@
         // 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",
     };
 
     /* Language bg: Bulgarian */
@@ -641,6 +668,9 @@
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
         /* 42 */ "\u0410\u0411\u0412",
+        /* 43 */ null,
+        // single_quotes of Bulgarian is default single_quotes_right_left.
+        /* 44 */ "!text/double_9qm_lqm",
     };
 
     /* Language ca: Catalan */
@@ -771,6 +801,14 @@
         // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
         // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
         /* 12 */ "\u017E,\u017A,\u017C",
+        /* 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",
     };
 
     /* Language da: Danish */
@@ -832,6 +870,14 @@
         /* 23 */ "\u00E4",
         // U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS
         /* 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",
     };
 
     /* Language de: German */
@@ -874,6 +920,15 @@
         // U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
         // U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE
         /* 6 */ "\u00F1,\u0144",
+        /* 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",
     };
 
     /* Language el: Greek */
@@ -1058,20 +1113,20 @@
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-        null, null, null, null, null, null, null, null, null, null, null, null, null,
-        /* ~107 */
-        /* 108 */ "q",
-        /* 109 */ "x",
+        null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~108 */
+        /* 109 */ "q",
+        /* 110 */ "x",
         // U+015D: "ŝ" LATIN SMALL LETTER S WITH CIRCUMFLEX
-        /* 110 */ "\u015D",
+        /* 111 */ "\u015D",
         // U+011D: "ĝ" LATIN SMALL LETTER G WITH CIRCUMFLEX
-        /* 111 */ "\u011D",
+        /* 112 */ "\u011D",
         // U+016D: "ŭ" LATIN SMALL LETTER U WITH BREVE
-        /* 112 */ "\u016D",
+        /* 113 */ "\u016D",
         // U+0109: "ĉ" LATIN SMALL LETTER C WITH CIRCUMFLEX
-        /* 113 */ "\u0109",
+        /* 114 */ "\u0109",
         // U+0135: "ĵ" LATIN SMALL LETTER J WITH CIRCUMFLEX
-        /* 114 */ "\u0135",
+        /* 115 */ "\u0135",
     };
 
     /* Language es: Spanish */
@@ -1129,25 +1184,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,
-        /* ~48 */
+        null, null, null, null, null, null, null, null, null, null, null, null,
+        /* ~49 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 49 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
-        /* 50~ */
+        /* 50 */ "!fixedColumnOrder!9,\u00A1,\",\',#,-,:,!,\\,,?,\u00BF,@,&,\\%,+,;,/,(,)",
+        /* 51~ */
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
         null, null, null, null, null, null, null,
-        /* ~101 */
+        /* ~102 */
         // U+00A1: "¡" INVERTED EXCLAMATION MARK
-        /* 102 */ "!,\u00A1",
-        /* 103 */ null,
+        /* 103 */ "!,\u00A1",
+        /* 104 */ null,
         // U+00BF: "¿" INVERTED QUESTION MARK
-        /* 104 */ "?,\u00BF",
-        /* 105 */ "\"",
-        /* 106 */ "\'",
+        /* 105 */ "?,\u00BF",
+        /* 106 */ "\"",
         /* 107 */ "\'",
+        /* 108 */ "\'",
     };
 
     /* Language et: Estonian */
@@ -1248,6 +1303,12 @@
         /* 22 */ "\u00E4",
         // U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE
         /* 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",
     };
 
     /* Language fa: Persian */
@@ -1264,55 +1325,36 @@
         // U+067E: "پ" ARABIC LETTER PEH
         /* 42 */ "\u0627\u200C\u0628\u200C\u067E",
         /* 43 */ null,
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
-        /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\",\'",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B",
-        /* 46 */ null,
-        // U+FDFC: "﷼" RIAL SIGN
-        // U+060B: "؋" AFGHANI SIGN
-        // U+00A2: "¢" CENT SIGN
-        // U+00A3: "£" POUND SIGN
-        // U+20AC: "€" EURO SIGN
-        // U+00A5: "¥" YEN SIGN
-        // U+20B1: "₱" PESO SIGN
-        /* 47 */ "\uFDFC",
-        /* 48 */ "$,\u00A2,\u20AC,\u00A3,\u00A5,\u20B1,\u060B",
+        /* 44 */ null,
+        /* 45 */ "!text/single_laqm_raqm_rtl",
+        /* 46 */ "!text/double_laqm_raqm_rtl",
+        /* 47~ */
+        null, null, null,
+        /* ~49 */
         // U+061F: "؟" ARABIC QUESTION MARK
         // U+060C: "،" ARABIC COMMA
         // U+061B: "؛" ARABIC SEMICOLON
-        /* 49 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
+        /* 50 */ "!fixedColumnOrder!8,\",\',#,-,:,!,\u060C,\u061F,@,&,\\%,+,\u061B,/,(,)",
         // U+2605: "★" BLACK STAR
         // U+066D: "٭" ARABIC FIVE POINTED STAR
-        /* 50 */ "\u2605,\u066D",
+        /* 51 */ "\u2605,\u066D",
         // U+266A: "♪" EIGHTH NOTE
-        /* 51 */ "\u266A",
-        /* 52 */ null,
+        /* 52 */ "\u266A",
+        /* 53 */ 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
-        /* 53 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
-        /* 54 */ "!fixedColumnOrder!4,\uFD3F|\uFD3E,>|<,}|{,]|[",
+        /* 54 */ "!fixedColumnOrder!4,\uFD3E|\uFD3F,<|>,{|},[|]",
+        /* 55 */ "!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
-        // The following characters don't need BIDI mirroring.
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
-        /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
+        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,<|>",
+        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,>|<",
         // U+0655: "ٕ" ARABIC HAMZA BELOW
         // U+0652: "ْ" ARABIC SUKUN
         // U+0651: "ّ" ARABIC SHADDA
@@ -1329,74 +1371,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.
-        /* 57 */ "!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",
-        /* 58 */ "\u064B",
+        /* 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",
         // U+06F1: "۱" EXTENDED ARABIC-INDIC DIGIT ONE
-        /* 59 */ "\u06F1",
+        /* 60 */ "\u06F1",
         // U+06F2: "۲" EXTENDED ARABIC-INDIC DIGIT TWO
-        /* 60 */ "\u06F2",
+        /* 61 */ "\u06F2",
         // U+06F3: "۳" EXTENDED ARABIC-INDIC DIGIT THREE
-        /* 61 */ "\u06F3",
+        /* 62 */ "\u06F3",
         // U+06F4: "۴" EXTENDED ARABIC-INDIC DIGIT FOUR
-        /* 62 */ "\u06F4",
+        /* 63 */ "\u06F4",
         // U+06F5: "۵" EXTENDED ARABIC-INDIC DIGIT FIVE
-        /* 63 */ "\u06F5",
+        /* 64 */ "\u06F5",
         // U+06F6: "۶" EXTENDED ARABIC-INDIC DIGIT SIX
-        /* 64 */ "\u06F6",
+        /* 65 */ "\u06F6",
         // U+06F7: "۷" EXTENDED ARABIC-INDIC DIGIT SEVEN
-        /* 65 */ "\u06F7",
+        /* 66 */ "\u06F7",
         // U+06F8: "۸" EXTENDED ARABIC-INDIC DIGIT EIGHT
-        /* 66 */ "\u06F8",
+        /* 67 */ "\u06F8",
         // U+06F9: "۹" EXTENDED ARABIC-INDIC DIGIT NINE
-        /* 67 */ "\u06F9",
+        /* 68 */ "\u06F9",
         // U+06F0: "۰" EXTENDED ARABIC-INDIC DIGIT ZERO
-        /* 68 */ "\u06F0",
+        /* 69 */ "\u06F0",
         // Label for "switch to symbols" key.
         // U+061F: "؟" ARABIC QUESTION MARK
-        /* 69 */ "\u06F3\u06F2\u06F1\u061F",
+        /* 70 */ "\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.
-        /* 70 */ "\u06F3\u06F2\u06F1",
-        /* 71 */ "1",
-        /* 72 */ "2",
-        /* 73 */ "3",
-        /* 74 */ "4",
-        /* 75 */ "5",
-        /* 76 */ "6",
-        /* 77 */ "7",
-        /* 78 */ "8",
-        /* 79 */ "9",
+        /* 71 */ "\u06F3\u06F2\u06F1",
+        /* 72 */ "1",
+        /* 73 */ "2",
+        /* 74 */ "3",
+        /* 75 */ "4",
+        /* 76 */ "5",
+        /* 77 */ "6",
+        /* 78 */ "7",
+        /* 79 */ "8",
+        /* 80 */ "9",
         // U+066B: "٫" ARABIC DECIMAL SEPARATOR
         // U+066C: "٬" ARABIC THOUSANDS SEPARATOR
-        /* 80 */ "0,\u066B,\u066C",
-        /* 81~ */
+        /* 81 */ "0,\u066B,\u066C",
+        /* 82~ */
         null, null, null, null, null, null, null, null, null, null,
-        /* ~90 */
+        /* ~91 */
         // U+060C: "،" ARABIC COMMA
-        /* 91 */ "\u060C",
-        /* 92 */ "\\,",
-        /* 93 */ "\u061F",
-        /* 94 */ "\u061B",
+        /* 92 */ "\u060C",
+        /* 93 */ "\\,",
+        /* 94 */ "\u061F",
+        /* 95 */ "\u061B",
         // U+066A: "٪" ARABIC PERCENT SIGN
-        /* 95 */ "\u066A",
-        /* 96 */ null,
-        /* 97 */ "?",
-        /* 98 */ ";",
+        /* 96 */ "\u066A",
+        /* 97 */ null,
+        /* 98 */ "?",
+        /* 99 */ ";",
         // U+2030: "‰" PER MILLE SIGN
-        /* 99 */ "\\%,\u2030",
+        /* 100 */ "\\%,\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
-        /* 100 */ "\u060C",
-        /* 101 */ "!",
-        /* 102 */ "!,\\,",
-        /* 103 */ "\u061F",
-        /* 104 */ "\u061F,?",
-        /* 105 */ "\u060C",
-        /* 106 */ "\u061F",
-        /* 107 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
+        /* 101 */ "\u060C",
+        /* 102 */ "!",
+        /* 103 */ "!,\\,",
+        /* 104 */ "\u061F",
+        /* 105 */ "\u061F,?",
+        /* 106 */ "\u060C",
+        /* 107 */ "\u061F",
+        /* 108 */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\u00AB|\u00BB,\u00BB|\u00AB",
     };
 
     /* Language fi: Finnish */
@@ -1512,48 +1554,48 @@
         // U+0917: "ग" DEVANAGARI LETTER GA
         /* 42 */ "\u0915\u0916\u0917",
         /* 43~ */
-        null, null, null, null,
-        /* ~46 */
+        null, null, null, null, null,
+        /* ~47 */
         // U+20B9: "₹" INDIAN RUPEE SIGN
-        /* 47 */ "\u20B9",
-        /* 48~ */
+        /* 48 */ "\u20B9",
+        /* 49~ */
         null, null, null, null, null, null, null, null, null, null, null,
-        /* ~58 */
+        /* ~59 */
         // U+0967: "१" DEVANAGARI DIGIT ONE
-        /* 59 */ "\u0967",
+        /* 60 */ "\u0967",
         // U+0968: "२" DEVANAGARI DIGIT TWO
-        /* 60 */ "\u0968",
+        /* 61 */ "\u0968",
         // U+0969: "३" DEVANAGARI DIGIT THREE
-        /* 61 */ "\u0969",
+        /* 62 */ "\u0969",
         // U+096A: "४" DEVANAGARI DIGIT FOUR
-        /* 62 */ "\u096A",
+        /* 63 */ "\u096A",
         // U+096B: "५" DEVANAGARI DIGIT FIVE
-        /* 63 */ "\u096B",
+        /* 64 */ "\u096B",
         // U+096C: "६" DEVANAGARI DIGIT SIX
-        /* 64 */ "\u096C",
+        /* 65 */ "\u096C",
         // U+096D: "७" DEVANAGARI DIGIT SEVEN
-        /* 65 */ "\u096D",
+        /* 66 */ "\u096D",
         // U+096E: "८" DEVANAGARI DIGIT EIGHT
-        /* 66 */ "\u096E",
+        /* 67 */ "\u096E",
         // U+096F: "९" DEVANAGARI DIGIT NINE
-        /* 67 */ "\u096F",
+        /* 68 */ "\u096F",
         // U+0966: "०" DEVANAGARI DIGIT ZERO
-        /* 68 */ "\u0966",
+        /* 69 */ "\u0966",
         // Label for "switch to symbols" key.
-        /* 69 */ "?\u0967\u0968\u0969",
+        /* 70 */ "?\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.
-        /* 70 */ "\u0967\u0968\u0969",
-        /* 71 */ "1",
-        /* 72 */ "2",
-        /* 73 */ "3",
-        /* 74 */ "4",
-        /* 75 */ "5",
-        /* 76 */ "6",
-        /* 77 */ "7",
-        /* 78 */ "8",
-        /* 79 */ "9",
-        /* 80 */ "0",
+        /* 71 */ "\u0967\u0968\u0969",
+        /* 72 */ "1",
+        /* 73 */ "2",
+        /* 74 */ "3",
+        /* 75 */ "4",
+        /* 76 */ "5",
+        /* 77 */ "6",
+        /* 78 */ "7",
+        /* 79 */ "8",
+        /* 80 */ "9",
+        /* 81 */ "0",
     };
 
     /* Language hr: Croatian */
@@ -1581,6 +1623,14 @@
         // U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
         // U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE
         /* 12 */ "\u017E,\u017A,\u017C",
+        /* 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",
     };
 
     /* Language hu: Hungarian */
@@ -1626,6 +1676,15 @@
         // U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
         // U+016B: "ū" LATIN SMALL LETTER U WITH MACRON
         /* 4 */ "\u00FA,\u00FC,\u0171,\u00FB,\u00F9,\u016B",
+        /* 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",
     };
 
     /* Language is: Icelandic */
@@ -1689,6 +1748,12 @@
         /* 21 */ "\u00E6",
         // U+00FE: "þ" LATIN SMALL LETTER THORN
         /* 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",
     };
 
     /* Language it: Italian */
@@ -1748,45 +1813,38 @@
         // U+05D1: "ב" HEBREW LETTER BET
         // U+05D2: "ג" HEBREW LETTER GIMEL
         /* 42 */ "\u05D0\u05D1\u05D2",
-        /* 43 */ null,
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
-        /* 44 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!4,\u201C,\u201D,\u00AB|\u00BB,\u00BB|\u00AB,\u2018,\u2019,\u201A,\u201B",
-        /* 46 */ null,
-        // U+20AA: "₪" NEW SHEQEL SIGN
-        /* 47 */ "\u20AA",
-        /* 48 */ null,
-        /* 49 */ null,
+        // The following characters don't need BIDI mirroring.
+        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
+        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
+        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+        // 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~ */
+        null, null, null, null,
+        /* ~50 */
         // U+2605: "★" BLACK STAR
-        /* 50 */ "\u2605",
-        /* 51 */ null,
+        /* 51 */ "\u2605",
+        /* 52 */ null,
         // U+00B1: "±" PLUS-MINUS SIGN
         // U+FB29: "﬩" HEBREW LETTER ALTERNATIVE PLUS SIGN
-        /* 52 */ "\u00B1,\uFB29",
+        /* 53 */ "\u00B1,\uFB29",
         // The all letters need to be mirrored are found at
         // http://www.unicode.org/Public/6.1.0/ucd/BidiMirroring.txt
-        /* 53 */ "!fixedColumnOrder!3,<|>,{|},[|]",
-        /* 54 */ "!fixedColumnOrder!3,>|<,}|{,]|[",
+        /* 54 */ "!fixedColumnOrder!3,<|>,{|},[|]",
+        /* 55 */ "!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
-        // The following characters don't need BIDI mirroring.
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        /* 55 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
-        /* 56 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
+        /* 56 */ "!fixedColumnOrder!3,\u2039|\u203A,\u2264|\u2265,\u00AB|\u00BB",
+        /* 57 */ "!fixedColumnOrder!3,\u203A|\u2039,\u2265|\u2264,\u00BB|\u00AB",
     };
 
     /* Language ka: Georgian */
@@ -1801,6 +1859,8 @@
         // U+10D1: "ბ" GEORGIAN LETTER BAN
         // U+10D2: "გ" GEORGIAN LETTER GAN
         /* 42 */ "\u10D0\u10D1\u10D2",
+        /* 43 */ "!text/single_9qm_lqm",
+        /* 44 */ "!text/double_9qm_lqm",
     };
 
     /* Language ky: Kirghiz */
@@ -1930,6 +1990,12 @@
         // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
         // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
         /* 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",
     };
 
     /* Language lv: Latvian */
@@ -2019,6 +2085,12 @@
         // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
         // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
         /* 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",
     };
 
     /* Language mk: Macedonian */
@@ -2045,21 +2117,8 @@
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
         /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ null,
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string>
-        /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B",
+        /* 43 */ "!text/single_9qm_lqm",
+        /* 44 */ "!text/double_9qm_lqm",
     };
 
     /* Language mn: Mongolian */
@@ -2075,10 +2134,10 @@
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
         /* 42 */ "\u0410\u0411\u0412",
         /* 43~ */
-        null, null, null, null,
-        /* ~46 */
+        null, null, null, null, null,
+        /* ~47 */
         // U+20AE: "₮" TUGRIK SIGN
-        /* 47 */ "\u20AE",
+        /* 48 */ "\u20AE",
     };
 
     /* Language nb: Norwegian Bokmål */
@@ -2126,6 +2185,12 @@
         /* 23 */ "\u00F6",
         // U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS
         /* 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",
     };
 
     /* Language nl: Dutch */
@@ -2177,6 +2242,13 @@
         /* 7 */ null,
         // U+0133: "ij" LATIN SMALL LIGATURE IJ
         /* 8 */ "\u0133",
+        /* 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",
     };
 
     /* Language pl: Polish */
@@ -2231,6 +2303,12 @@
         /* 13 */ null,
         // U+0142: "ł" LATIN SMALL LETTER L WITH STROKE
         /* 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",
     };
 
     /* Language pt: Portuguese */
@@ -2330,6 +2408,13 @@
         /* ~10 */
         // U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW
         /* 11 */ "\u021B",
+        /* 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",
     };
 
     /* Language ru: Russian */
@@ -2364,6 +2449,8 @@
         // 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",
     };
 
     /* Language sk: Slovak */
@@ -2454,6 +2541,14 @@
         // U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
         // U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE
         /* 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",
     };
 
     /* Language sl: Slovenian */
@@ -2474,6 +2569,14 @@
         /* 11 */ null,
         // U+017E: "ž" LATIN SMALL LETTER Z WITH CARON
         /* 12 */ "\u017E",
+        /* 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",
     };
 
     /* Language sr: Serbian */
@@ -2519,21 +2622,10 @@
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
         /* 42 */ "\u0410\u0411\u0412",
-        /* 43 */ null,
-        // U+2018: "‘" LEFT SINGLE QUOTATION MARK
-        // U+2019: "’" RIGHT SINGLE QUOTATION MARK
-        // U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-        // U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-        // U+201C: "“" LEFT DOUBLE QUOTATION MARK
-        // U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-        // U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-        // U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string>
-        /* 44 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB",
-        // TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK.
-        // <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-        /* 45 */ "!fixedColumnOrder!5,\u201E,\u201C,\u201D,\u00AB,\u00BB,\u2018,\u2019,\u201A,\u201B",
+        /* 43 */ "!text/single_9qm_lqm",
+        /* 44 */ "!text/double_9qm_lqm",
+        /* 45 */ "!text/single_raqm_laqm",
+        /* 46 */ "!text/double_raqm_laqm",
     };
 
     /* Language sv: Swedish */
@@ -2576,6 +2668,12 @@
         /* 23 */ "\u00F8",
         // U+00E6: "æ" LATIN SMALL LETTER AE
         /* 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",
     };
 
     /* Language sw: Swahili */
@@ -2642,10 +2740,10 @@
         // U+0E04: "ค" THAI CHARACTER KHO KHWAI
         /* 42 */ "\u0E01\u0E02\u0E04",
         /* 43~ */
-        null, null, null, null,
-        /* ~46 */
+        null, null, null, null, null,
+        /* ~47 */
         // U+0E3F: "฿" THAI CURRENCY SYMBOL BAHT
-        /* 47 */ "\u0E3F",
+        /* 48 */ "\u0E3F",
     };
 
     /* Language tl: Tagalog */
@@ -2780,11 +2878,13 @@
         // U+0411: "Б" CYRILLIC CAPITAL LETTER BE
         // U+0412: "В" CYRILLIC CAPITAL LETTER VE
         /* 42 */ "\u0410\u0411\u0412",
-        /* 43~ */
-        null, null, null, null,
-        /* ~46 */
+        /* 43 */ "!text/single_9qm_lqm",
+        /* 44 */ "!text/double_9qm_lqm",
+        /* 45~ */
+        null, null, null,
+        /* ~47 */
         // U+20B4: "₴" HRYVNIA SIGN
-        /* 47 */ "\u20B4",
+        /* 48 */ "\u20B4",
     };
 
     /* Language vi: Vietnamese */
@@ -2869,10 +2969,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,
-        /* ~46 */
+        null, null, null, null, null, null, null, null,
+        /* ~47 */
         // U+20AB: "₫" DONG SIGN
-        /* 47 */ "\u20AB",
+        /* 48 */ "\u20AB",
     };
 
     /* Language zu: Zulu */
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
index ad31633..ab2a12f 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin;
 
-import android.content.Context;
 import android.text.TextUtils;
 import android.util.SparseArray;
 
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index d9d664f..0d0ce57 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -74,6 +74,8 @@
 
     // The path fragment to append after the client ID for dictionary info requests.
     private static final String QUERY_PATH_DICT_INFO = "dict";
+    // The path fragment to append after the client ID for dictionary datafile requests.
+    private static final String QUERY_PATH_DATAFILE = "datafile";
     // The path fragment to append after the client ID for updating the metadata URI.
     private static final String QUERY_PATH_METADATA = "metadata";
     private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid";
@@ -156,7 +158,7 @@
                 c.close();
                 return Collections.<WordListInfo>emptyList();
             }
-            final List<WordListInfo> list = CollectionUtils.newArrayList();
+            final ArrayList<WordListInfo> list = CollectionUtils.newArrayList();
             do {
                 final String wordListId = c.getString(0);
                 final String wordListLocale = c.getString(1);
@@ -186,13 +188,18 @@
     /**
      * Helper method to encapsulate exception handling.
      */
-    private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver,
-            final Uri uri) {
+    private static AssetFileDescriptor openAssetFileDescriptor(
+            final ContentProviderClient providerClient, final Uri uri) {
         try {
-            return resolver.openAssetFileDescriptor(uri, "r");
+            return providerClient.openAssetFile(uri, "r");
         } catch (FileNotFoundException e) {
-            // I don't want to log the word list URI here for security concerns
-            Log.e(TAG, "Could not find a word list from the dictionary provider.");
+            // I don't want to log the word list URI here for security concerns. The exception
+            // contains the name of the file, so let's not pass it to Log.e here.
+            Log.e(TAG, "Could not find a word list from the dictionary provider."
+                    /* intentionally don't pass the exception (see comment above) */);
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
             return null;
         }
     }
@@ -202,9 +209,8 @@
      * to the cache file name designated by its id and locale, overwriting it if already present
      * and creating it (and its containing directory) if necessary.
      */
-    private static AssetFileAddress cacheWordList(final String id, final String locale,
-            final ContentResolver resolver, final Context context) {
-
+    private static AssetFileAddress cacheWordList(final String wordlistId, final String locale,
+            final ContentProviderClient providerClient, final Context context) {
         final int COMPRESSED_CRYPTED_COMPRESSED = 0;
         final int CRYPTED_COMPRESSED = 1;
         final int COMPRESSED_CRYPTED = 2;
@@ -214,11 +220,20 @@
         final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED;
         final int MODE_MAX = NONE;
 
-        final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
-        final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+        final String clientId = context.getString(R.string.dictionary_pack_client_id);
+        final Uri.Builder wordListUriBuilder;
+        try {
+            wordListUriBuilder = getContentUriBuilderForType(clientId,
+                    providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Can't communicate with the dictionary pack", e);
+            return null;
+        }
+        final String finalFileName =
+                DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context);
         String tempFileName;
         try {
-            tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+            tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context);
         } catch (IOException e) {
             Log.e(TAG, "Can't open the temporary file", e);
             return null;
@@ -236,7 +251,7 @@
             final Uri wordListUri = wordListUriBuilder.build();
             try {
                 // Open input.
-                afd = openAssetFileDescriptor(resolver, wordListUri);
+                afd = openAssetFileDescriptor(providerClient, wordListUri);
                 // If we can't open it at all, don't even try a number of times.
                 if (null == afd) return null;
                 originalSourceStream = afd.createInputStream();
@@ -284,10 +299,10 @@
                 }
                 wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                         QUERY_PARAMETER_SUCCESS);
-                if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
+                if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
                     Log.e(TAG, "Could not have the dictionary pack delete a word list");
                 }
-                BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, finalFile);
+                BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile);
                 // Success! Close files (through the finally{} clause) and return.
                 return AssetFileAddress.makeFromFileName(finalFileName);
             } catch (Exception e) {
@@ -327,8 +342,12 @@
         // as invalid.
         wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT,
                 QUERY_PARAMETER_FAILURE);
-        if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) {
-            Log.e(TAG, "In addition, we were unable to delete it.");
+        try {
+            if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) {
+                Log.e(TAG, "In addition, we were unable to delete it.");
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "In addition, communication with the dictionary provider was cut", e);
         }
         return null;
     }
@@ -345,17 +364,27 @@
      */
     public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale,
             final Context context, final boolean hasDefaultWordList) {
-        final ContentResolver resolver = context.getContentResolver();
-        final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
-                hasDefaultWordList);
-        final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
-        for (WordListInfo id : idList) {
-            final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context);
-            if (null != afd) {
-                fileAddressList.add(afd);
-            }
+        final ContentProviderClient providerClient = context.getContentResolver().
+                acquireContentProviderClient(getProviderUriBuilder("").build());
+        if (null == providerClient) {
+            Log.e(TAG, "Can't establish communication with the dictionary provider");
+            return CollectionUtils.newArrayList();
         }
-        return fileAddressList;
+        try {
+            final List<WordListInfo> idList = getWordListWordListInfos(locale, context,
+                    hasDefaultWordList);
+            final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList();
+            for (WordListInfo id : idList) {
+                final AssetFileAddress afd =
+                        cacheWordList(id.mId, id.mLocale, providerClient, context);
+                if (null != afd) {
+                    fileAddressList.add(afd);
+                }
+            }
+            return fileAddressList;
+        } finally {
+            providerClient.release();
+        }
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
index a96738b..e913f28 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java
@@ -68,9 +68,13 @@
     /**
      * Generates a unique temporary file name in the app cache directory.
      */
-    public static String getTempFileName(String id, Context context) throws IOException {
-        return File.createTempFile(DictionaryInfoUtils.replaceFileNameDangerousCharacters(id),
-                null).getAbsolutePath();
+    public static String getTempFileName(final String id, final Context context)
+            throws IOException {
+        final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id);
+        // If the first argument is less than three chars, createTempFile throws a
+        // RuntimeException. We don't really care about what name we get, so just
+        // put a three-chars prefix makes us safe.
+        return File.createTempFile("xxx" + safeId, null).getAbsolutePath();
     }
 
     /**
diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java
index 422448e..50e5023 100644
--- a/java/src/com/android/inputmethod/latin/Constants.java
+++ b/java/src/com/android/inputmethod/latin/Constants.java
@@ -179,14 +179,13 @@
     public static final int CODE_DELETE = -4;
     public static final int CODE_SETTINGS = -5;
     public static final int CODE_SHORTCUT = -6;
-    public static final int CODE_ACTION_ENTER = -7;
-    public static final int CODE_ACTION_NEXT = -8;
-    public static final int CODE_ACTION_PREVIOUS = -9;
-    public static final int CODE_LANGUAGE_SWITCH = -10;
-    public static final int CODE_RESEARCH = -11;
-    public static final int CODE_SHIFT_ENTER = -12;
+    public static final int CODE_ACTION_NEXT = -7;
+    public static final int CODE_ACTION_PREVIOUS = -8;
+    public static final int CODE_LANGUAGE_SWITCH = -9;
+    public static final int CODE_RESEARCH = -10;
+    public static final int CODE_SHIFT_ENTER = -11;
     // Code value representing the code is not specified.
-    public static final int CODE_UNSPECIFIED = -13;
+    public static final int CODE_UNSPECIFIED = -12;
 
     public static boolean isLetterCode(final int code) {
         return code >= CODE_SPACE;
@@ -200,7 +199,6 @@
         case CODE_DELETE: return "delete";
         case CODE_SETTINGS: return "settings";
         case CODE_SHORTCUT: return "shortcut";
-        case CODE_ACTION_ENTER: return "actionEnter";
         case CODE_ACTION_NEXT: return "actionNext";
         case CODE_ACTION_PREVIOUS: return "actionPrevious";
         case CODE_LANGUAGE_SWITCH: return "languageSwitch";
diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java
index 7df266e..c2aade6 100644
--- a/java/src/com/android/inputmethod/latin/DebugSettings.java
+++ b/java/src/com/android/inputmethod/latin/DebugSettings.java
@@ -57,7 +57,7 @@
         if (usabilityStudyPref instanceof CheckBoxPreference) {
             final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref;
             checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE,
-                    ResearchLogger.DEFAULT_USABILITY_STUDY_MODE));
+                    LatinImeLogger.getUsabilityStudyMode(prefs)));
             checkbox.setSummary(R.string.settings_warning_researcher_mode);
         }
         final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING);
diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
index d2a946b..dcfa483 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java
@@ -41,8 +41,6 @@
     private static final String RESOURCE_PACKAGE_NAME =
             DictionaryInfoUtils.class.getPackage().getName();
     private static final String DEFAULT_MAIN_DICT = "main";
-    private static final String ID_CATEGORY_SEPARATOR =
-            BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR;
     private static final String MAIN_DICT_PREFIX = "main_";
     // 6 digits - unicode is limited to 21 bits
     private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6;
@@ -51,24 +49,28 @@
         private static final String LOCALE_COLUMN = "locale";
         private static final String WORDLISTID_COLUMN = "id";
         private static final String LOCAL_FILENAME_COLUMN = "filename";
+        private static final String DESCRIPTION_COLUMN = "description";
         private static final String DATE_COLUMN = "date";
         private static final String FILESIZE_COLUMN = "filesize";
         private static final String VERSION_COLUMN = "version";
+        public final String mId;
         public final Locale mLocale;
+        public final String mDescription;
         public final AssetFileAddress mFileAddress;
         public final int mVersion;
-        public final String mId;
-        public DictionaryInfo(final Locale locale, final AssetFileAddress fileAddress,
-                final int version) {
+        public DictionaryInfo(final String id, final Locale locale, final String description,
+                final AssetFileAddress fileAddress, final int version) {
+            mId = id;
             mLocale = locale;
+            mDescription = description;
             mFileAddress = fileAddress;
             mVersion = version;
-            mId = DEFAULT_MAIN_DICT + ID_CATEGORY_SEPARATOR + mLocale;
         }
         public ContentValues toContentValues() {
             final ContentValues values = new ContentValues();
             values.put(WORDLISTID_COLUMN, mId);
             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);
@@ -283,9 +285,11 @@
             final AssetFileAddress fileAddress) {
         final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull(
                 new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength);
+        final String id = header.getId();
         final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
+        final String description = header.getDescription();
         final String version = header.getVersion();
-        return new DictionaryInfo(locale, fileAddress, Integer.parseInt(version));
+        return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
     }
 
     private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList,
diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
index 2ad619b..ecb2014 100644
--- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java
+++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java
@@ -106,18 +106,13 @@
     }
 
     public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) {
-        final int actionId = editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
         if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) {
             return EditorInfo.IME_ACTION_NONE;
         } else if (editorInfo.actionLabel != null) {
             return IME_ACTION_CUSTOM_LABEL;
         } else {
-            return actionId;
+            // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId"
+            return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION;
         }
     }
-
-    public static int getConcreteActionIdFromEditorInfo(final EditorInfo editorInfo) {
-        final int actionId = getImeOptionsActionIdFromEditorInfo(editorInfo);
-        return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? editorInfo.actionId : actionId;
-    }
 }
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 73ace2b..252fb02 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -132,6 +132,8 @@
     private View mKeyPreviewBackingView;
     private View mSuggestionsContainer;
     private SuggestionStripView mSuggestionStripView;
+    // Never null
+    private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     @UsedForTesting Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
     private ApplicationInfo mTargetApplicationInfo;
@@ -165,7 +167,6 @@
     private boolean mExpectingUpdateSelection;
     private int mDeleteCount;
     private long mLastKeyTime;
-    private int mActionId;
     private TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet();
 
     // Member variables for remembering the current device orientation.
@@ -427,7 +428,7 @@
         initSuggest();
 
         if (ProductionFlag.IS_EXPERIMENTAL) {
-            ResearchLogger.getInstance().init(this, mKeyboardSwitcher);
+            ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mSuggest);
         }
         mDisplayOrientation = getResources().getConfiguration().orientation;
 
@@ -562,6 +563,9 @@
         }
         mSettings.onDestroy();
         unregisterReceiver(mReceiver);
+        if (ProductionFlag.IS_EXPERIMENTAL) {
+            ResearchLogger.getInstance().onDestroy();
+        }
         // TODO: The experimental version is not supported by the Dictionary Pack Service yet.
         if (!ProductionFlag.IS_EXPERIMENTAL) {
             unregisterReceiver(mDictionaryPackInstallReceiver);
@@ -729,6 +733,7 @@
             // otherwise it will clear the suggestion strip.
             setPunctuationSuggestions();
         }
+        mSuggestedWords = SuggestedWords.EMPTY;
 
         mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart);
 
@@ -756,7 +761,6 @@
 
         mLastSelectionStart = editorInfo.initialSelStart;
         mLastSelectionEnd = editorInfo.initialSelEnd;
-        mActionId = InputTypeUtils.getConcreteActionIdFromEditorInfo(editorInfo);
 
         mHandler.cancelUpdateSuggestionStrip();
         mHandler.cancelDoubleSpacePeriodTimer();
@@ -954,6 +958,10 @@
         LatinImeLogger.commit();
         mKeyboardSwitcher.onHideWindow();
 
+        if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) {
+            AccessibleKeyboardViewProxy.getInstance().onHideWindow();
+        }
+
         if (TRACE) Debug.stopMethodTracing();
         if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
             mOptionsDialog.dismiss();
@@ -994,7 +1002,7 @@
                 false /* isPrediction */);
         // When in fullscreen mode, show completions generated by the application
         final boolean isAutoCorrection = false;
-        setSuggestionStrip(suggestedWords, isAutoCorrection);
+        setSuggestedWords(suggestedWords, isAutoCorrection);
         setAutoCorrectionIndicator(isAutoCorrection);
         setSuggestionStripShown(true);
         if (ProductionFlag.IS_EXPERIMENTAL) {
@@ -1119,7 +1127,7 @@
         if (mSettings.getCurrent().mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestionStrip(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
         }
         mConnection.resetCachesUponCursorMove(newCursorPosition);
     }
@@ -1393,13 +1401,28 @@
                 ResearchLogger.getInstance().onResearchKeySelected(this);
             }
             break;
-        case Constants.CODE_ACTION_ENTER:
-            if (EditorInfo.IME_ACTION_NONE != mActionId
-                && EditorInfo.IME_ACTION_UNSPECIFIED != mActionId) {
-                performEditorAction(mActionId);
-                break;
+        case Constants.CODE_ENTER:
+            final EditorInfo editorInfo = getCurrentInputEditorInfo();
+            final int imeOptionsActionId =
+                    InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo);
+            if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) {
+                // Either we have an actionLabel and we should performEditorAction with actionId
+                // regardless of its value.
+                performEditorAction(editorInfo.actionId);
+            } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) {
+                // We didn't have an actionLabel, but we had another action to execute.
+                // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast,
+                // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it
+                // means there should be an action and the app didn't bother to set a specific
+                // code for it - presumably it only handles one. It does not have to be treated
+                // in any specific way: anything that is not IME_ACTION_NONE should be sent to
+                // performEditorAction.
+                performEditorAction(imeOptionsActionId);
+            } else {
+                // No action label, and the action from imeOptions is NONE: this is a regular
+                // enter key that should input a carriage return.
+                didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
             }
-            didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
             break;
         case Constants.CODE_SHIFT_ENTER:
             didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState);
@@ -1967,8 +1990,8 @@
     // Outside LatinIME, only used by the test suite.
     @UsedForTesting
     boolean isShowingPunctuationList() {
-        if (mSuggestionStripView == null) return false;
-        return mSettings.getCurrent().mSuggestPuncList == mSuggestionStripView.getSuggestions();
+        if (mSuggestedWords == null) return false;
+        return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords;
     }
 
     private boolean isSuggestionsStripVisible() {
@@ -1984,11 +2007,12 @@
     }
 
     private void clearSuggestionStrip() {
-        setSuggestionStrip(SuggestedWords.EMPTY, false);
+        setSuggestedWords(SuggestedWords.EMPTY, false);
         setAutoCorrectionIndicator(false);
     }
 
-    private void setSuggestionStrip(final SuggestedWords words, final boolean isAutoCorrection) {
+    private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) {
+        mSuggestedWords = words;
         if (mSuggestionStripView != null) {
             mSuggestionStripView.setSuggestions(words);
             mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection);
@@ -2071,15 +2095,16 @@
     }
 
     private SuggestedWords getOlderSuggestions(final String typedWord) {
-        SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions();
-        if (previousSuggestions == mSettings.getCurrent().mSuggestPuncList) {
-            previousSuggestions = SuggestedWords.EMPTY;
+        SuggestedWords previousSuggestedWords = mSuggestedWords;
+        if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) {
+            previousSuggestedWords = SuggestedWords.EMPTY;
         }
         if (typedWord == null) {
-            return previousSuggestions;
+            return previousSuggestedWords;
         }
         final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions =
-                SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, previousSuggestions);
+                SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord,
+                        previousSuggestedWords);
         return new SuggestedWords(typedWordAndPreviousSuggestions,
                 false /* typedWordValid */,
                 false /* hasAutoCorrectionCandidate */,
@@ -2101,7 +2126,7 @@
         }
         mWordComposer.setAutoCorrection(autoCorrection);
         final boolean isAutoCorrection = suggestedWords.willAutoCorrect();
-        setSuggestionStrip(suggestedWords, isAutoCorrection);
+        setSuggestedWords(suggestedWords, isAutoCorrection);
         setAutoCorrectionIndicator(isAutoCorrection);
         setSuggestionStripShown(isSuggestionsStripVisible());
     }
@@ -2124,7 +2149,7 @@
                 Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer);
             }
             if (ProductionFlag.IS_EXPERIMENTAL) {
-                final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
+                final SuggestedWords suggestedWords = mSuggestedWords;
                 ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection,
                         separatorString, mWordComposer.isBatchMode(), suggestedWords);
             }
@@ -2149,7 +2174,7 @@
     // interface
     @Override
     public void pickSuggestionManually(final int index, final String suggestion) {
-        final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
+        final SuggestedWords suggestedWords = mSuggestedWords;
         // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput
         if (suggestion.length() == 1 && isShowingPunctuationList()) {
             // Word separators are suggested before the user inputs something.
@@ -2181,6 +2206,7 @@
         if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn()
                 && mApplicationSpecifiedCompletions != null
                 && index >= 0 && index < mApplicationSpecifiedCompletions.length) {
+            mSuggestedWords = SuggestedWords.EMPTY;
             if (mSuggestionStripView != null) {
                 mSuggestionStripView.clear();
             }
@@ -2236,7 +2262,7 @@
      */
     private void commitChosenWord(final String chosenWord, final int commitType,
             final String separatorString) {
-        final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions();
+        final SuggestedWords suggestedWords = mSuggestedWords;
         mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan(
                 this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1);
         // Add the word to the user history dictionary
@@ -2253,7 +2279,7 @@
         if (mSettings.getCurrent().mBigramPredictionEnabled) {
             clearSuggestionStrip();
         } else {
-            setSuggestionStrip(mSettings.getCurrent().mSuggestPuncList, false);
+            setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false);
         }
         setAutoCorrectionIndicator(false);
         setSuggestionStripShown(isSuggestionsStripVisible());
@@ -2550,6 +2576,12 @@
         dialog.show();
     }
 
+    // TODO: can this be removed somehow without breaking the tests?
+    @UsedForTesting
+    /* package for test */ String getFirstSuggestedWord() {
+        return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null;
+    }
+
     public void debugDumpStateAndCrashWithException(final String context) {
         final StringBuilder s = new StringBuilder();
         s.append("Target application : ").append(mTargetApplicationInfo.name)
diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
index e4e8b94..3f2b0a3 100644
--- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java
+++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java
@@ -37,6 +37,10 @@
     public static void commit() {
     }
 
+    public static boolean getUsabilityStudyMode(final SharedPreferences prefs) {
+        return false;
+    }
+
     public static void onDestroy() {
     }
 
diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java
index fcf7270..5fde815 100644
--- a/java/src/com/android/inputmethod/latin/LocaleUtils.java
+++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java
@@ -180,14 +180,15 @@
             synchronized (sLockForRunInLocale) {
                 final Configuration conf = res.getConfiguration();
                 final Locale oldLocale = conf.locale;
+                final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale));
                 try {
-                    if (newLocale != null && !newLocale.equals(oldLocale)) {
+                    if (needsChange) {
                         conf.locale = newLocale;
                         res.updateConfiguration(conf, null);
                     }
                     return job(res);
                 } finally {
-                    if (newLocale != null && !newLocale.equals(oldLocale)) {
+                    if (needsChange) {
                         conf.locale = oldLocale;
                         res.updateConfiguration(conf, null);
                     }
diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java
index 02b44c7..435074b 100644
--- a/java/src/com/android/inputmethod/latin/Settings.java
+++ b/java/src/com/android/inputmethod/latin/Settings.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.preference.PreferenceManager;
 
@@ -64,6 +65,7 @@
     public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
     public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
             "pref_gesture_floating_preview_text";
+    public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
 
     public static final String PREF_INPUT_LANGUAGE = "input_language";
     public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
@@ -260,4 +262,16 @@
     public static boolean readUseFullscreenMode(final Resources res) {
         return res.getBoolean(R.bool.config_use_fullscreen_mode);
     }
+
+    public static boolean readShowSetupWizardIcon(final SharedPreferences prefs,
+            final Context context) {
+        if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
+            final ApplicationInfo appInfo = context.getApplicationInfo();
+            final boolean isApplicationInSystemImage =
+                    (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            // Default value
+            return !isApplicationInSystemImage;
+        }
+        return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java
index edd064c..4c90e48 100644
--- a/java/src/com/android/inputmethod/latin/SettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java
@@ -31,6 +31,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.inputmethod.latin.define.ProductionFlag;
+import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
 import com.android.inputmethodcommon.InputMethodSettingsFragment;
 
 public final class SettingsFragment extends InputMethodSettingsFragment
@@ -155,6 +156,10 @@
             removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen());
         }
 
+        final CheckBoxPreference showSetupWizardIcon =
+                (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
+        showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, context));
+
         setupKeyLongpressTimeoutSettings(prefs, res);
         setupKeypressVibrationDurationSettings(prefs, res);
         setupKeypressSoundVolumeSettings(prefs, res);
@@ -196,6 +201,8 @@
             final boolean gestureInputEnabled = Settings.readGestureInputEnabled(prefs, res);
             setPreferenceEnabled(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled);
             setPreferenceEnabled(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled);
+        } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
+            LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
         }
         ensureConsistencyOfAutoCorrectionSettings();
         updateVoiceModeSummary();
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
index 81bc9f5..5280283 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java
@@ -263,9 +263,10 @@
             UserHistoryDictIOUtils.readDictionaryBinary(
                     new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener);
         } catch (FileNotFoundException e) {
-            Log.e(TAG, "when loading: file not found" + e);
+            // This is an expected condition: we don't have a user history dictionary for this
+            // language yet. It will be created sometime later.
         } catch (IOException e) {
-            Log.e(TAG, "IOException when open bytebuffer: " + e);
+            Log.e(TAG, "IOException on opening a bytebuffer", e);
         } finally {
             if (inStream != null) {
                 try {
@@ -328,7 +329,7 @@
                     Thread.sleep(15000);
                     Log.w(TAG, "End stress in closing");
                 } catch (InterruptedException e) {
-                    Log.e(TAG, "In stress test: " + e);
+                    Log.e(TAG, "In stress test", e);
                 }
             }
 
@@ -343,7 +344,7 @@
                 out.flush();
                 out.close();
             } catch (IOException e) {
-                Log.e(TAG, "IO Exception while writing file: " + e);
+                Log.e(TAG, "IO Exception while writing file", e);
             } finally {
                 if (out != null) {
                     try {
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index acfcd53..7a604dc 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -28,6 +28,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.io.BufferedReader;
@@ -77,6 +78,7 @@
         private RingCharBuffer() {
             // Intentional empty constructor for singleton.
         }
+        @UsedForTesting
         public static RingCharBuffer getInstance() {
             return sRingCharBuffer;
         }
@@ -93,6 +95,7 @@
             return ret < 0 ? ret + BUFSIZE : ret;
         }
         // TODO: accept code points
+        @UsedForTesting
         public void push(char c, int x, int y) {
             if (!mEnabled) return;
             mCharBuf[mEnd] = c;
diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java
index 31f616d..f7cb434 100644
--- a/java/src/com/android/inputmethod/latin/WordComposer.java
+++ b/java/src/com/android/inputmethod/latin/WordComposer.java
@@ -16,6 +16,7 @@
 
 package com.android.inputmethod.latin;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
 
@@ -177,7 +178,8 @@
     /**
      * Internal method to retrieve reasonable proximity info for a character.
      */
-    private void addKeyInfo(final int codePoint, final Keyboard keyboard) {
+    @UsedForTesting
+    public void addKeyInfo(final int codePoint, final Keyboard keyboard) {
         final int x, y;
         final Key key;
         if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
index 83acca8..e1e5e55 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java
@@ -258,6 +258,8 @@
         public final FormatOptions mFormatOptions;
         private static final String DICTIONARY_VERSION_ATTRIBUTE = "version";
         private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale";
+        private static final String DICTIONARY_ID_ATTRIBUTE = "dictionary";
+        private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description";
         public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
                 final FormatOptions formatOptions) {
             mHeaderSize = headerSize;
@@ -274,6 +276,18 @@
         public String getVersion() {
             return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE);
         }
+
+        // Helper method to get the dictionary ID as a String
+        public String getId() {
+            return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE);
+        }
+
+        // Helper method to get the description
+        public String getDescription() {
+            // TODO: Right now each dictionary file comes with a description in its own language.
+            // It will display as is no matter the device's locale. It should be internationalized.
+            return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE);
+        }
     }
 
     private FormatSpec() {
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
new file mode 100644
index 0000000..1b893a6
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -0,0 +1,122 @@
+/*
+ * 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.setup;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.Process;
+import android.preference.PreferenceManager;
+import android.util.Log;
+
+import com.android.inputmethod.compat.IntentCompatUtils;
+import com.android.inputmethod.latin.RichInputMethodManager;
+import com.android.inputmethod.latin.Settings;
+
+/**
+ * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME
+ * package has been replaced by a newer version of the same package. This class also detects
+ * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent.
+ *
+ * If this IME has already been installed in the system image and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it
+ * will hide the setup wizard's icon.
+ *
+ * If this IME has already been installed in the data partition and a new version of this IME has
+ * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it
+ * will not hide the setup wizard's icon, and the icon will appear on the launcher.
+ *
+ * If this IME hasn't been installed yet and has been newly installed, no
+ * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear
+ * on the launcher.
+ *
+ * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this
+ * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher
+ * depending on which partition this IME is installed.
+ *
+ * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received
+ * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on
+ * the launcher depending on which partition this IME is installed.
+ */
+public final class LauncherIconVisibilityManager extends BroadcastReceiver {
+    private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName();
+
+    @Override
+    public void onReceive(final Context context, final Intent intent) {
+        if (shouldHandleThisIntent(intent, context)) {
+            updateSetupWizardIconVisibility(context);
+        }
+
+        // The process that hosts this broadcast receiver is invoked and remains alive even after
+        // 1) the package has been re-installed, 2) the device has been booted,
+        // 3) a multiuser has been created.
+        // There is no good reason to keep the process alive if this IME isn't a current IME.
+        RichInputMethodManager.init(context);
+        if (!SetupActivity.isThisImeCurrent(context)) {
+            final int myPid = Process.myPid();
+            Log.i(TAG, "Killing my process: pid=" + myPid);
+            Process.killProcess(myPid);
+        }
+    }
+
+    private static boolean shouldHandleThisIntent(final Intent intent, final Context context) {
+        final String action = intent.getAction();
+        if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
+            Log.i(TAG, "Package has been replaced: " + context.getPackageName());
+            return true;
+        } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
+            Log.i(TAG, "Boot has been completed");
+            return true;
+        } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) {
+            Log.i(TAG, "User initialize");
+            return true;
+        }
+        return false;
+    }
+
+    public static void updateSetupWizardIconVisibility(final Context context) {
+        final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class);
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+        final boolean stateHasSet;
+        if (Settings.readShowSetupWizardIcon(prefs, context)) {
+            stateHasSet = setActivityState(context, setupWizardActivity,
+                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+            Log.i(TAG, (stateHasSet ? "Enable activity: " : "Activity has already been enabled: ")
+                    + setupWizardActivity);
+        } else {
+            stateHasSet = setActivityState(context, setupWizardActivity,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+            Log.i(TAG, (stateHasSet ? "Disable activity: " : "Activity has already been disabled: ")
+                    + setupWizardActivity);
+        }
+    }
+
+    private static boolean setActivityState(final Context context,
+            final ComponentName activityComponent, final int activityState) {
+        final PackageManager pm = context.getPackageManager();
+        final int activityComponentState = pm.getComponentEnabledSetting(activityComponent);
+        if (activityComponentState == activityState) {
+            return false;
+        }
+        pm.setComponentEnabledSetting(
+                activityComponent, activityState, PackageManager.DONT_KILL_APP);
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
index fab8945..e009fbc 100644
--- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
+++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java
@@ -17,23 +17,341 @@
 package com.android.inputmethod.latin.setup;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Message;
+import android.provider.Settings;
+import android.view.View;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
 
+import com.android.inputmethod.compat.TextViewCompatUtils;
+import com.android.inputmethod.compat.ViewCompatUtils;
+import com.android.inputmethod.latin.CollectionUtils;
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.RichInputMethodManager;
 import com.android.inputmethod.latin.SettingsActivity;
+import com.android.inputmethod.latin.StaticInnerHandlerWrapper;
+
+import java.util.HashMap;
 
 public final class SetupActivity extends Activity {
+    private SetupStepIndicatorView mStepIndicatorView;
+    private final SetupStepGroup mSetupSteps = new SetupStepGroup();
+    private static final String STATE_STEP = "step";
+    private int mStepNumber;
+    private static final int STEP_1 = 1;
+    private static final int STEP_2 = 2;
+    private static final int STEP_3 = 3;
+
+    private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this);
+
+    static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> {
+        private static final int MSG_POLLING_IME_SETTINGS = 0;
+        private static final long IME_SETTINGS_POLLING_INTERVAL = 200;
+
+        public SettingsPoolingHandler(final SetupActivity outerInstance) {
+            super(outerInstance);
+        }
+
+        @Override
+        public void handleMessage(final Message msg) {
+            final SetupActivity setupActivity = getOuterInstance();
+            switch (msg.what) {
+            case MSG_POLLING_IME_SETTINGS:
+                if (SetupActivity.isThisImeEnabled(setupActivity)) {
+                    setupActivity.invokeSetupWizardOfThisIme();
+                    return;
+                }
+                startPollingImeSettings();
+                break;
+            }
+        }
+
+        public void startPollingImeSettings() {
+            sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS),
+                    IME_SETTINGS_POLLING_INTERVAL);
+        }
+
+        public void cancelPollingImeSettings() {
+            removeMessages(MSG_POLLING_IME_SETTINGS);
+        }
+    }
+
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
+    protected void onCreate(final Bundle savedInstanceState) {
+        setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar);
         super.onCreate(savedInstanceState);
 
-        // TODO: Implement setup wizard.
+        setContentView(R.layout.setup_wizard);
+
+        RichInputMethodManager.init(this);
+
+        if (savedInstanceState == null) {
+            mStepNumber = determineSetupStepNumber();
+        } else {
+            mStepNumber = savedInstanceState.getInt(STATE_STEP);
+        }
+
+        if (mStepNumber == STEP_3) {
+            // This IME already has been enabled and set as current IME.
+            // TODO: Implement tutorial.
+            invokeSettingsOfThisIme();
+            finish();
+            return;
+        }
+
+        // TODO: Use sans-serif-thin font family depending on the system locale white list and
+        // the SDK version.
+        final TextView titleView = (TextView)findViewById(R.id.setup_title);
+        titleView.setText(getString(R.string.setup_title, getString(R.string.english_ime_name)));
+
+        mStepIndicatorView = (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator);
+
+        final SetupStep step1 = new SetupStep(findViewById(R.id.setup_step1),
+                R.string.setup_step1_title, R.string.setup_step1_instruction,
+                R.drawable.ic_settings_language, R.string.language_settings);
+        step1.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeLanguageAndInputSettings();
+                mHandler.startPollingImeSettings();
+            }
+        });
+        mSetupSteps.addStep(STEP_1, step1);
+
+        final SetupStep step2 = new SetupStep(findViewById(R.id.setup_step2),
+                R.string.setup_step2_title, R.string.setup_step2_instruction,
+                0 /* actionIcon */, R.string.select_input_method);
+        step2.setAction(new Runnable() {
+            @Override
+            public void run() {
+                // Invoke input method picker.
+                RichInputMethodManager.getInstance().getInputMethodManager()
+                        .showInputMethodPicker();
+            }
+        });
+        mSetupSteps.addStep(STEP_2, step2);
+
+        final SetupStep step3 = new SetupStep(findViewById(R.id.setup_step3),
+                R.string.setup_step3_title, 0 /* instruction */,
+                R.drawable.sym_keyboard_language_switch, R.string.setup_step3_instruction);
+        step3.setAction(new Runnable() {
+            @Override
+            public void run() {
+                invokeSubtypeEnablerOfThisIme();
+            }
+        });
+        mSetupSteps.addStep(STEP_3, step3);
+    }
+
+    private void invokeSetupWizardOfThisIme() {
         final Intent intent = new Intent();
-        intent.setClass(this, SettingsActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+        intent.setClass(this, SetupActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         startActivity(intent);
-        finish();
+    }
+
+    private void invokeSettingsOfThisIme() {
+        final Intent intent = new Intent();
+        intent.setClass(this, SettingsActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        startActivity(intent);
+    }
+
+    private void invokeLanguageAndInputSettings() {
+        final Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        startActivity(intent);
+    }
+
+    private void invokeSubtypeEnablerOfThisIme() {
+        final InputMethodInfo imi =
+                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+        final Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
+        intent.addCategory(Intent.CATEGORY_DEFAULT);
+        intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId());
+        startActivity(intent);
+    }
+
+    /**
+     * Check if the IME specified by the context is enabled.
+     * Note that {@link RichInputMethodManager} must have been initialized before calling this
+     * method.
+     *
+     * @param context package context of the IME to be checked.
+     * @return true if this IME is enabled.
+     */
+    public static boolean isThisImeEnabled(final Context context) {
+        final String packageName = context.getPackageName();
+        final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager();
+        for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) {
+            if (packageName.equals(imi.getPackageName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if the IME specified by the context is the current IME.
+     * Note that {@link RichInputMethodManager} must have been initialized before calling this
+     * method.
+     *
+     * @param context package context of the IME to be checked.
+     * @return true if this IME is the current IME.
+     */
+    public static boolean isThisImeCurrent(final Context context) {
+        final InputMethodInfo myImi =
+                RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme();
+        final String currentImeId = Settings.Secure.getString(
+                context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        return myImi.getId().equals(currentImeId);
+    }
+
+    private int determineSetupStepNumber() {
+        mHandler.cancelPollingImeSettings();
+        if (!isThisImeEnabled(this)) {
+            return STEP_1;
+        }
+        if (!isThisImeCurrent(this)) {
+            return STEP_2;
+        }
+        return STEP_3;
+    }
+
+    @Override
+    protected void onSaveInstanceState(final Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt(STATE_STEP, mStepNumber);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mStepNumber = savedInstanceState.getInt(STATE_STEP);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mStepNumber = determineSetupStepNumber();
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        mStepNumber = determineSetupStepNumber();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        updateSetupStepView();
+    }
+
+    @Override
+    public void onWindowFocusChanged(final boolean hasFocus) {
+        super.onWindowFocusChanged(hasFocus);
+        if (!hasFocus) {
+            return;
+        }
+        mStepNumber = determineSetupStepNumber();
+        updateSetupStepView();
+    }
+
+    private void updateSetupStepView() {
+        final int layoutDirection = ViewCompatUtils.getLayoutDirection(mStepIndicatorView);
+        mStepIndicatorView.setIndicatorPosition(
+                getIndicatorPosition(mStepNumber, mSetupSteps.getTotalStep(), layoutDirection));
+        mSetupSteps.enableStep(mStepNumber);
+    }
+
+    private static float getIndicatorPosition(final int step, final int totalStep,
+            final int layoutDirection) {
+        final float pos = ((step - STEP_1) * 2 + 1) / (float)(totalStep * 2);
+        return (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos;
+    }
+
+    static final class SetupStep implements View.OnClickListener {
+        private final View mRootView;
+        private final TextView mActionLabel;
+        private Runnable mAction;
+
+        public SetupStep(final View rootView, final int title, final int instruction,
+                final int actionIcon, final int actionLabel) {
+            mRootView = rootView;
+            final Resources res = rootView.getResources();
+            final String applicationName = res.getString(R.string.english_ime_name);
+
+            final TextView titleView = (TextView)rootView.findViewById(R.id.setup_step_title);
+            titleView.setText(res.getString(title, applicationName));
+
+            final TextView instructionView = (TextView)rootView.findViewById(
+                    R.id.setup_step_instruction);
+            if (instruction == 0) {
+                instructionView.setVisibility(View.GONE);
+            } else {
+                instructionView.setText(res.getString(instruction, applicationName));
+            }
+
+            mActionLabel = (TextView)rootView.findViewById(R.id.setup_step_action_label);
+            mActionLabel.setText(res.getString(actionLabel));
+            if (actionIcon == 0) {
+                final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel);
+                ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0);
+            } else {
+                final int overrideColor = res.getColor(R.color.setup_text_action);
+                final Drawable icon = res.getDrawable(actionIcon);
+                icon.setColorFilter(overrideColor, PorterDuff.Mode.MULTIPLY);
+                icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
+                TextViewCompatUtils.setCompoundDrawablesRelative(
+                        mActionLabel, icon, null, null, null);
+            }
+        }
+
+        public void setEnabled(final boolean enabled) {
+            mRootView.setVisibility(enabled ? View.VISIBLE : View.GONE);
+        }
+
+        public void setAction(final Runnable action) {
+            mActionLabel.setOnClickListener(this);
+            mAction = action;
+        }
+
+        @Override
+        public void onClick(final View v) {
+            if (mAction != null) {
+                mAction.run();
+            }
+        }
+    }
+
+    static final class SetupStepGroup {
+        private final HashMap<Integer, SetupStep> mGroup = CollectionUtils.newHashMap();
+
+        public void addStep(final int stepNo, final SetupStep step) {
+            mGroup.put(stepNo, step);
+        }
+
+        public void enableStep(final int enableStepNo) {
+            for (final Integer stepNo : mGroup.keySet()) {
+                final SetupStep step = mGroup.get(stepNo);
+                step.setEnabled(stepNo == enableStepNo);
+            }
+        }
+
+        public int getTotalStep() {
+            return mGroup.size();
+        }
     }
 }
diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
new file mode 100644
index 0000000..077a217
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java
@@ -0,0 +1,56 @@
+/*
+ * 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.setup;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.inputmethod.latin.R;
+
+public final class SetupStepIndicatorView extends View {
+    private final Path mIndicatorPath = new Path();
+    private final Paint mIndicatorPaint = new Paint();
+    private float mXRatio;
+
+    public SetupStepIndicatorView(final Context context, final AttributeSet attrs) {
+        super(context, attrs);
+        mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background));
+        mIndicatorPaint.setStyle(Paint.Style.FILL);
+    }
+
+    public void setIndicatorPosition(final float xRatio) {
+        mXRatio = xRatio;
+        invalidate();
+    }
+
+    @Override
+    protected void onDraw(final Canvas canvas) {
+        super.onDraw(canvas);
+        final int xPos = (int)(getWidth() * mXRatio);
+        final int height = getHeight();
+        mIndicatorPath.rewind();
+        mIndicatorPath.moveTo(xPos, 0);
+        mIndicatorPath.lineTo(xPos + height, height);
+        mIndicatorPath.lineTo(xPos - height, height);
+        mIndicatorPath.close();
+        canvas.drawPath(mIndicatorPath, mIndicatorPaint);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index bc51d5d..5a29eee 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -644,10 +644,6 @@
         return false;
     }
 
-    public SuggestedWords getSuggestions() {
-        return mSuggestedWords;
-    }
-
     public void clear() {
         mSuggestionsStrip.removeAllViews();
         removeAllViews();
diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java
index 1a9a720..839e2b7 100644
--- a/java/src/com/android/inputmethod/research/LogUnit.java
+++ b/java/src/com/android/inputmethod/research/LogUnit.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.research;
 
-import android.content.SharedPreferences;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.JsonWriter;
@@ -45,7 +44,7 @@
  * will not violate the user's privacy.  Checks for this may include whether other LogUnits have
  * been published recently, or whether the LogUnit contains numbers, etc.
  */
-/* package */ class LogUnit {
+public class LogUnit {
     private static final String TAG = LogUnit.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
 
@@ -151,10 +150,10 @@
                     continue;
                 }
                 // Only retrieve the jsonWriter if we need to.  If we don't get this far, then
-                // researchLog.getValidJsonWriterLocked() will not ever be called, and the file
-                // will not have been opened for writing.
+                // researchLog.getInitializedJsonWriterLocked() will not ever be called, and the
+                // file will not have been opened for writing.
                 if (jsonWriter == null) {
-                    jsonWriter = researchLog.getValidJsonWriterLocked();
+                    jsonWriter = researchLog.getInitializedJsonWriterLocked();
                     outputLogUnitStart(jsonWriter, canIncludePrivateData);
                 }
                 logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i));
diff --git a/java/src/com/android/inputmethod/research/LoggingUtils.java b/java/src/com/android/inputmethod/research/LoggingUtils.java
new file mode 100644
index 0000000..1261d67
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/LoggingUtils.java
@@ -0,0 +1,38 @@
+/*
+ * 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.research;
+
+import android.view.MotionEvent;
+
+/* package */ class LoggingUtils {
+    private LoggingUtils() {
+        // This utility class is not publicly instantiable.
+    }
+
+    /* package */ static String getMotionEventActionTypeString(final int actionType) {
+        switch (actionType) {
+        case MotionEvent.ACTION_CANCEL: return "CANCEL";
+        case MotionEvent.ACTION_UP: return "UP";
+        case MotionEvent.ACTION_DOWN: return "DOWN";
+        case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP";
+        case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN";
+        case MotionEvent.ACTION_MOVE: return "MOVE";
+        case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE";
+        default: return "ACTION_" + actionType;
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java
index 3a87bf1..9aa60f8 100644
--- a/java/src/com/android/inputmethod/research/MainLogBuffer.java
+++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java
@@ -18,6 +18,7 @@
 
 import android.util.Log;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.latin.Dictionary;
 import com.android.inputmethod.latin.Suggest;
 import com.android.inputmethod.latin.define.ProductionFlag;
@@ -64,16 +65,11 @@
     // The size of the n-grams logged.  E.g. N_GRAM_SIZE = 2 means to sample bigrams.
     public static final int N_GRAM_SIZE = 2;
 
-    // Whether all words should be recorded, leaving unsampled word between bigrams.  Useful for
-    // testing.
-    /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
-            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
-
-    // The number of words between n-grams to omit from the log.
-    private static final int DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES =
-            IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
-
-    private Suggest mSuggest;
+    // TODO: Remove dependence on Suggest, and pass in Dictionary as a parameter to an appropriate
+    // method.
+    private final Suggest mSuggest;
+    @UsedForTesting
+    private Dictionary mDictionaryForTesting;
     private boolean mIsStopping = false;
 
     /* package for test */ int mNumWordsBetweenNGrams;
@@ -82,15 +78,25 @@
     // after a sample is taken.
     /* package for test */ int mNumWordsUntilSafeToSample;
 
-    public MainLogBuffer() {
-        super(N_GRAM_SIZE + DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES);
-        mNumWordsBetweenNGrams = DEFAULT_NUMBER_OF_WORDS_BETWEEN_SAMPLES;
-        final Random random = new Random();
-        mNumWordsUntilSafeToSample = DEBUG ? 0 : random.nextInt(mNumWordsBetweenNGrams + 1);
+    public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore,
+            final Suggest suggest) {
+        super(N_GRAM_SIZE + wordsBetweenSamples);
+        mNumWordsBetweenNGrams = wordsBetweenSamples;
+        mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore;
+        mSuggest = suggest;
     }
 
-    public void setSuggest(final Suggest suggest) {
-        mSuggest = suggest;
+    @UsedForTesting
+    /* package for test */ void setDictionaryForTesting(final Dictionary dictionary) {
+        mDictionaryForTesting = dictionary;
+    }
+
+    private Dictionary getDictionary() {
+        if (mDictionaryForTesting != null) {
+            return mDictionaryForTesting;
+        }
+        if (mSuggest == null || !mSuggest.hasMainDictionary()) return null;
+        return mSuggest.getMainDictionary();
     }
 
     public void resetWordCounter() {
@@ -114,7 +120,7 @@
      */
     private boolean isSafeNGram(final ArrayList<LogUnit> logUnits, final int minNGramSize) {
         // Bypass privacy checks when debugging.
-        if (IS_LOGGING_EVERYTHING) {
+        if (ResearchLogger.IS_LOGGING_EVERYTHING) {
             if (mIsStopping) {
                 return true;
             }
@@ -137,18 +143,15 @@
         if (mNumWordsUntilSafeToSample > 0) {
             return false;
         }
-        if (mSuggest == null || !mSuggest.hasMainDictionary()) {
+        // Reload the dictionary in case it has changed (e.g., because the user has changed
+        // languages).
+        final Dictionary dictionary = getDictionary();
+        if (dictionary == null) {
             // Main dictionary is unavailable.  Since we cannot check it, we cannot tell if a
             // word is out-of-vocabulary or not.  Therefore, we must judge the entire buffer
             // contents to potentially pose a privacy risk.
             return false;
         }
-        // Reload the dictionary in case it has changed (e.g., because the user has changed
-        // languages).
-        final Dictionary dictionary = mSuggest.getMainDictionary();
-        if (dictionary == null) {
-            return false;
-        }
 
         // Check each word in the buffer.  If any word poses a privacy threat, we cannot upload
         // the complete buffer contents in detail.
@@ -220,10 +223,10 @@
             final boolean canIncludePrivateData);
 
     @Override
-    protected void shiftOutWords(int numWords) {
-        int oldNumActualWords = getNumActualWords();
+    protected void shiftOutWords(final int numWords) {
+        final int oldNumActualWords = getNumActualWords();
         super.shiftOutWords(numWords);
-        int numWordsShifted = oldNumActualWords - getNumActualWords();
+        final int numWordsShifted = oldNumActualWords - getNumActualWords();
         mNumWordsUntilSafeToSample -= numWordsShifted;
         if (DEBUG) {
             Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample);
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 5114977..9016e23 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -38,12 +38,19 @@
 /**
  * Logs the use of the LatinIME keyboard.
  *
- * This class logs operations on the IME keyboard, including what the user has typed.
- * Data is stored locally in a file in app-specific storage.
+ * This class logs operations on the IME keyboard, including what the user has typed.  Data is
+ * written to a {@link JsonWriter}, which will write to a local file.
+ *
+ * The JsonWriter is created on-demand by calling {@link #getInitializedJsonWriterLocked}.
+ *
+ * This class uses an executor to perform file-writing operations on a separate thread.  It also
+ * tries to avoid creating unnecessary files if there is nothing to write.  It also handles
+ * flushing, making sure it happens, but not too frequently.
  *
  * This functionality is off by default. See {@link ProductionFlag#IS_EXPERIMENTAL}.
  */
 public class ResearchLog {
+    // TODO: Automatically initialize the JsonWriter rather than requiring the caller to manage it.
     private static final String TAG = ResearchLog.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
     private static final long FLUSH_DELAY_IN_MS = 1000 * 5;
@@ -87,6 +94,12 @@
         mContext = context;
     }
 
+    /**
+     * Waits for any publication requests to finish and closes the {@link JsonWriter} used for
+     * output.
+     *
+     * See class comment for details about {@code JsonWriter} construction.
+     */
     public synchronized void close(final Runnable onClosed) {
         mExecutor.submit(new Callable<Object>() {
             @Override
@@ -94,20 +107,15 @@
                 try {
                     if (mHasWrittenData) {
                         mJsonWriter.endArray();
-                        mJsonWriter.flush();
-                        mJsonWriter.close();
-                        if (DEBUG) {
-                            Log.d(TAG, "wrote log to " + mFile);
-                        }
                         mHasWrittenData = false;
-                    } else {
-                        if (DEBUG) {
-                            Log.d(TAG, "close() called, but no data, not outputting");
-                        }
+                    }
+                    mJsonWriter.flush();
+                    mJsonWriter.close();
+                    if (DEBUG) {
+                        Log.d(TAG, "wrote log to " + mFile);
                     }
                 } catch (Exception e) {
-                    Log.d(TAG, "error when closing ResearchLog:");
-                    e.printStackTrace();
+                    Log.d(TAG, "error when closing ResearchLog:", e);
                 } finally {
                     if (mFile != null && mFile.exists()) {
                         mFile.setWritable(false, false);
@@ -125,6 +133,12 @@
 
     private boolean mIsAbortSuccessful;
 
+    /**
+     * Waits for publication requests to finish, closes the {@link JsonWriter}, but then deletes the
+     * backing file used for output.
+     *
+     * See class comment for details about {@code JsonWriter} construction.
+     */
     public synchronized void abort() {
         mExecutor.submit(new Callable<Object>() {
             @Override
@@ -184,6 +198,12 @@
         mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS);
     }
 
+    /**
+     * Queues up {@code logUnit} to be published in the background.
+     *
+     * @param logUnit the {@link LogUnit} to be published
+     * @param canIncludePrivateData whether private data in the LogUnit should be included
+     */
     public synchronized void publish(final LogUnit logUnit, final boolean canIncludePrivateData) {
         try {
             mExecutor.submit(new Callable<Object>() {
@@ -206,29 +226,39 @@
      * Return a JsonWriter for this ResearchLog.  It is initialized the first time this method is
      * called.  The cached value is returned in future calls.
      */
-    public JsonWriter getValidJsonWriterLocked() {
+    public JsonWriter getInitializedJsonWriterLocked() {
+        if (mJsonWriter != NULL_JSON_WRITER || mFile == null) return mJsonWriter;
         try {
-            if (mJsonWriter == NULL_JSON_WRITER && mFile != null) {
-                final FileOutputStream fos =
-                        mContext.openFileOutput(mFile.getName(), Context.MODE_PRIVATE);
-                mJsonWriter = new JsonWriter(new BufferedWriter(new OutputStreamWriter(fos)));
-                mJsonWriter.beginArray();
+            final JsonWriter jsonWriter = createJsonWriter(mContext, mFile);
+            if (jsonWriter != null) {
+                jsonWriter.beginArray();
+                mJsonWriter = jsonWriter;
                 mHasWrittenData = true;
             }
-        } catch (IOException e) {
-            e.printStackTrace();
-            Log.w(TAG, "Error in JsonWriter; disabling logging");
+        } catch (final IOException e) {
+            Log.w(TAG, "Error in JsonWriter; disabling logging", e);
             try {
                 mJsonWriter.close();
-            } catch (IllegalStateException e1) {
+            } catch (final IllegalStateException e1) {
                 // Assume that this is just the json not being terminated properly.
                 // Ignore
-            } catch (IOException e1) {
-                e1.printStackTrace();
+            } catch (final IOException e1) {
+                Log.w(TAG, "Error in closing JsonWriter; disabling logging", e1);
             } finally {
                 mJsonWriter = NULL_JSON_WRITER;
             }
         }
         return mJsonWriter;
     }
+
+    /**
+     * Create the JsonWriter to write the ResearchLog to.
+     *
+     * This method may be overriden in testing to redirect the output.
+     */
+    /* package for test */ JsonWriter createJsonWriter(final Context context, final File file)
+            throws IOException {
+        return new JsonWriter(new BufferedWriter(new OutputStreamWriter(
+                context.openFileOutput(file.getName(), Context.MODE_PRIVATE))));
+    }
 }
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 4521291..e705ddd 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -88,6 +88,7 @@
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
+import java.util.Random;
 import java.util.UUID;
 
 /**
@@ -121,31 +122,36 @@
     // field holds a channel name, the developer does not have to re-enter it when using the
     // feedback mechanism to generate multiple tests.
     private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false;
-    public static final boolean DEFAULT_USABILITY_STUDY_MODE = false;
     /* package */ static boolean sIsLogging = false;
     private static final int OUTPUT_FORMAT_VERSION = 5;
     private static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode";
-    private static final String PREF_RESEARCH_HAS_SEEN_SPLASH = "pref_research_has_seen_splash";
     /* package */ static final String LOG_FILENAME_PREFIX = "researchLog";
     private static final String LOG_FILENAME_SUFFIX = ".txt";
     /* package */ static final String USER_RECORDING_FILENAME_PREFIX = "recording";
     private static final String USER_RECORDING_FILENAME_SUFFIX = ".txt";
     private static final SimpleDateFormat TIMESTAMP_DATEFORMAT =
             new SimpleDateFormat("yyyyMMddHHmmssS", Locale.US);
+    // Whether all words should be recorded, leaving unsampled word between bigrams.  Useful for
+    // testing.
+    /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false
+            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+    // The number of words between n-grams to omit from the log.
+    private static final int NUMBER_OF_WORDS_BETWEEN_SAMPLES =
+            IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18);
+
     // Whether to show an indicator on the screen that logging is on.  Currently a very small red
     // dot in the lower right hand corner.  Most users should not notice it.
     private static final boolean IS_SHOWING_INDICATOR = true;
     // Change the default indicator to something very visible.  Currently two red vertical bars on
     // either side of they keyboard.
     private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false ||
-            (MainLogBuffer.IS_LOGGING_EVERYTHING && ProductionFlag.IS_EXPERIMENTAL_DEBUG);
+            (IS_LOGGING_EVERYTHING && ProductionFlag.IS_EXPERIMENTAL_DEBUG);
     // FEEDBACK_WORD_BUFFER_SIZE should add 1 because it must also hold the feedback LogUnit itself.
     public static final int FEEDBACK_WORD_BUFFER_SIZE = (Integer.MAX_VALUE - 1) + 1;
 
     // constants related to specific log points
     private static final String WHITESPACE_SEPARATORS = " \t\n\r";
     private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1
-    private static final String PREF_RESEARCH_LOGGER_UUID_STRING = "pref_research_logger_uuid";
     private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel";
 
     private static final ResearchLogger sInstance = new ResearchLogger();
@@ -153,7 +159,6 @@
     private static String sAllowedAccountDomain = null;
     // to write to a different filename, e.g., for testing, set mFile before calling start()
     /* package */ File mFilesDir;
-    /* package */ String mUUIDString;
     /* package */ ResearchLog mMainResearchLog;
     // mFeedbackLog records all events for the session, private or not (excepting
     // passwords).  It is written to permanent storage only if the user explicitly commands
@@ -199,7 +204,7 @@
     private Intent mUploadIntent;
     private Intent mUploadNowIntent;
 
-    private LogUnit mCurrentLogUnit = new LogUnit();
+    /* package for test */ LogUnit mCurrentLogUnit = new LogUnit();
 
     // Gestured or tapped words may be committed after the gesture of the next word has started.
     // To ensure that the gesture data of the next word is not associated with the previous word,
@@ -228,50 +233,44 @@
         return sInstance;
     }
 
-    public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) {
+    public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher,
+            final Suggest suggest) {
         assert latinIME != null;
-        if (latinIME == null) {
-            Log.w(TAG, "IMS is null; logging is off");
-        } else {
-            mFilesDir = latinIME.getFilesDir();
-            if (mFilesDir == null || !mFilesDir.exists()) {
-                Log.w(TAG, "IME storage directory does not exist.");
-            }
+        mLatinIME = latinIME;
+        mFilesDir = latinIME.getFilesDir();
+        if (mFilesDir == null || !mFilesDir.exists()) {
+            Log.w(TAG, "IME storage directory does not exist.  Cannot start logging.");
+            return;
         }
-        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
-        if (prefs != null) {
-            mUUIDString = getUUID(prefs);
-            if (!prefs.contains(PREF_USABILITY_STUDY_MODE)) {
-                Editor e = prefs.edit();
-                e.putBoolean(PREF_USABILITY_STUDY_MODE, DEFAULT_USABILITY_STUDY_MODE);
-                e.apply();
-            }
-            sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false);
-            prefs.registerOnSharedPreferenceChangeListener(this);
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME);
+        mPrefs.registerOnSharedPreferenceChangeListener(this);
 
-            final long lastCleanupTime = prefs.getLong(PREF_LAST_CLEANUP_TIME, 0L);
-            final long now = System.currentTimeMillis();
-            if (lastCleanupTime + DURATION_BETWEEN_DIR_CLEANUP_IN_MS < now) {
-                final long timeHorizon = now - MAX_LOGFILE_AGE_IN_MS;
-                cleanupLoggingDir(mFilesDir, timeHorizon);
-                Editor e = prefs.edit();
-                e.putLong(PREF_LAST_CLEANUP_TIME, now);
-                e.apply();
-            }
-        }
+        // Initialize fields from preferences
+        sIsLogging = ResearchSettings.readResearchLoggerEnabledFlag(mPrefs);
+
+        // Initialize fields from resources
         final Resources res = latinIME.getResources();
         sAccountType = res.getString(R.string.research_account_type);
         sAllowedAccountDomain = res.getString(R.string.research_allowed_account_domain);
-        mLatinIME = latinIME;
-        mPrefs = prefs;
+
+        // Cleanup logging directory
+        // TODO: Move this and other file-related components to separate file.
+        final long lastCleanupTime = mPrefs.getLong(PREF_LAST_CLEANUP_TIME, 0L);
+        final long now = System.currentTimeMillis();
+        if (now - lastCleanupTime > DURATION_BETWEEN_DIR_CLEANUP_IN_MS) {
+            final long timeHorizon = now - MAX_LOGFILE_AGE_IN_MS;
+            cleanupLoggingDir(mFilesDir, timeHorizon);
+            mPrefs.edit().putLong(PREF_LAST_CLEANUP_TIME, now).apply();
+        }
+
+        // Initialize external services
         mUploadIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent = new Intent(mLatinIME, UploaderService.class);
         mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true);
-        mReplayer.setKeyboardSwitcher(keyboardSwitcher);
-
         if (ProductionFlag.IS_EXPERIMENTAL) {
             scheduleUploadingService(mLatinIME);
         }
+        mReplayer.setKeyboardSwitcher(keyboardSwitcher);
     }
 
     /**
@@ -313,14 +312,16 @@
         mMainKeyboardView = null;
     }
 
-    private boolean hasSeenSplash() {
-        return mPrefs.getBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, false);
+    public void onDestroy() {
+        if (mPrefs != null) {
+            mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+        }
     }
 
     private Dialog mSplashDialog = null;
 
     private void maybeShowSplashScreen() {
-        if (hasSeenSplash()) {
+        if (ResearchSettings.readHasSeenSplash(mPrefs)) {
             return;
         }
         if (mSplashDialog != null && mSplashDialog.isShowing()) {
@@ -373,32 +374,23 @@
     }
 
     public void onUserLoggingConsent() {
-        setLoggingAllowed(true);
         if (mPrefs == null) {
-            return;
+            mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME);
+            if (mPrefs == null) return;
         }
-        final Editor e = mPrefs.edit();
-        e.putBoolean(PREF_RESEARCH_HAS_SEEN_SPLASH, true);
-        e.apply();
+        sIsLogging = true;
+        ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, true);
+        ResearchSettings.writeHasSeenSplash(mPrefs, true);
         restart();
     }
 
-    private void setLoggingAllowed(boolean enableLogging) {
-        if (mPrefs == null) {
-            return;
-        }
-        Editor e = mPrefs.edit();
-        e.putBoolean(PREF_USABILITY_STUDY_MODE, enableLogging);
-        e.apply();
-        sIsLogging = enableLogging;
-    }
-
     private static int sLogFileCounter = 0;
 
     private File createLogFile(final File filesDir) {
         final StringBuilder sb = new StringBuilder();
         sb.append(LOG_FILENAME_PREFIX).append('-');
-        sb.append(mUUIDString).append('-');
+        final String uuid = ResearchSettings.readResearchLoggerUuid(mPrefs);
+        sb.append(uuid).append('-');
         sb.append(TIMESTAMP_DATEFORMAT.format(new Date())).append('-');
         // Sometimes logFiles are created within milliseconds of each other.  Append a counter to
         // separate these.
@@ -416,7 +408,8 @@
     private File createUserRecordingFile(final File filesDir) {
         final StringBuilder sb = new StringBuilder();
         sb.append(USER_RECORDING_FILENAME_PREFIX).append('-');
-        sb.append(mUUIDString).append('-');
+        final String uuid = ResearchSettings.readResearchLoggerUuid(mPrefs);
+        sb.append(uuid).append('-');
         sb.append(TIMESTAMP_DATEFORMAT.format(new Date()));
         sb.append(USER_RECORDING_FILENAME_SUFFIX);
         return new File(filesDir, sb.toString());
@@ -458,17 +451,15 @@
             // Log.w(TAG, "not in usability mode; not logging");
             return;
         }
-        if (mFilesDir == null || !mFilesDir.exists()) {
-            Log.w(TAG, "IME storage directory does not exist.  Cannot start logging.");
-            return;
-        }
         if (mMainLogBuffer == null) {
             mMainResearchLog = new ResearchLog(createLogFile(mFilesDir), mLatinIME);
-            mMainLogBuffer = new MainLogBuffer() {
+            final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1);
+            mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore,
+                    mSuggest) {
                 @Override
                 protected void publish(final ArrayList<LogUnit> logUnits,
                         boolean canIncludePrivateData) {
-                    canIncludePrivateData |= MainLogBuffer.IS_LOGGING_EVERYTHING;
+                    canIncludePrivateData |= IS_LOGGING_EVERYTHING;
                     final int length = logUnits.size();
                     for (int i = 0; i < length; i++) {
                         final LogUnit logUnit = logUnits.get(i);
@@ -487,7 +478,6 @@
                     }
                 }
             };
-            mMainLogBuffer.setSuggest(mSuggest);
         }
         if (mFeedbackLogBuffer == null) {
             resetFeedbackLogging();
@@ -564,7 +554,7 @@
     }
 
     @Override
-    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
         if (key == null || prefs == null) {
             return;
         }
@@ -586,7 +576,7 @@
         presentFeedbackDialog(latinIME);
     }
 
-    public void presentFeedbackDialog(LatinIME latinIME) {
+    public void presentFeedbackDialog(final LatinIME latinIME) {
         if (isMakingUserRecording()) {
             saveRecording();
         }
@@ -818,9 +808,7 @@
             if (mPrefs == null) {
                 return;
             }
-            final Editor e = mPrefs.edit();
-            e.putString(PREF_RESEARCH_SAVED_CHANNEL, channelName);
-            e.apply();
+            mPrefs.edit().putString(PREF_RESEARCH_SAVED_CHANNEL, channelName).apply();
         }
     }
 
@@ -835,10 +823,13 @@
         mInFeedbackDialog = false;
     }
 
-    public void initSuggest(Suggest suggest) {
+    public void initSuggest(final Suggest suggest) {
         mSuggest = suggest;
+        // MainLogBuffer has out-of-date Suggest object.  Need to close it down and create a new
+        // one.
         if (mMainLogBuffer != null) {
-            mMainLogBuffer.setSuggest(mSuggest);
+            stop();
+            start();
         }
     }
 
@@ -1127,18 +1118,6 @@
         }
     }
 
-    private static String getUUID(final SharedPreferences prefs) {
-        String uuidString = prefs.getString(PREF_RESEARCH_LOGGER_UUID_STRING, null);
-        if (null == uuidString) {
-            UUID uuid = UUID.randomUUID();
-            uuidString = uuid.toString();
-            Editor editor = prefs.edit();
-            editor.putString(PREF_RESEARCH_LOGGER_UUID_STRING, uuidString);
-            editor.apply();
-        }
-        return uuidString;
-    }
-
     private String scrubWord(String word) {
         final Dictionary dictionary = getDictionary();
         if (dictionary == null) {
@@ -1185,12 +1164,12 @@
                         0);
                 final Integer versionCode = packageInfo.versionCode;
                 final String versionName = packageInfo.versionName;
+                final String uuid = ResearchSettings.readResearchLoggerUuid(researchLogger.mPrefs);
                 researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL,
-                        researchLogger.mUUIDString, editorInfo.packageName,
-                        Integer.toHexString(editorInfo.inputType),
+                        uuid, editorInfo.packageName, Integer.toHexString(editorInfo.inputType),
                         Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId,
                         Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName,
-                        OUTPUT_FORMAT_VERSION, MainLogBuffer.IS_LOGGING_EVERYTHING,
+                        OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING,
                         ProductionFlag.IS_EXPERIMENTAL_DEBUG);
             } catch (NameNotFoundException e) {
                 e.printStackTrace();
@@ -1226,17 +1205,7 @@
     public static void mainKeyboardView_processMotionEvent(final MotionEvent me, final int action,
             final long eventTime, final int index, final int id, final int x, final int y) {
         if (me != null) {
-            final String actionString;
-            switch (action) {
-                case MotionEvent.ACTION_CANCEL: actionString = "CANCEL"; break;
-                case MotionEvent.ACTION_UP: actionString = "UP"; break;
-                case MotionEvent.ACTION_DOWN: actionString = "DOWN"; break;
-                case MotionEvent.ACTION_POINTER_UP: actionString = "POINTER_UP"; break;
-                case MotionEvent.ACTION_POINTER_DOWN: actionString = "POINTER_DOWN"; break;
-                case MotionEvent.ACTION_MOVE: actionString = "MOVE"; break;
-                case MotionEvent.ACTION_OUTSIDE: actionString = "OUTSIDE"; break;
-                default: actionString = "ACTION_" + action; break;
-            }
+            final String actionString = LoggingUtils.getMotionEventActionTypeString(action);
             final ResearchLogger researchLogger = getInstance();
             researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT,
                     actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me));
diff --git a/java/src/com/android/inputmethod/research/ResearchSettings.java b/java/src/com/android/inputmethod/research/ResearchSettings.java
new file mode 100644
index 0000000..11e9ac7
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/ResearchSettings.java
@@ -0,0 +1,61 @@
+/*
+ * 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.research;
+
+import android.content.SharedPreferences;
+
+import java.util.UUID;
+
+public final class ResearchSettings {
+    public static final String PREF_RESEARCH_LOGGER_UUID = "pref_research_logger_uuid";
+    public static final String PREF_RESEARCH_LOGGER_ENABLED_FLAG =
+            "pref_research_logger_enabled_flag";
+    public static final String PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH =
+            "pref_research_logger_has_seen_splash";
+
+    private ResearchSettings() {
+        // Intentional empty constructor for singleton.
+    }
+
+    public static String readResearchLoggerUuid(final SharedPreferences prefs) {
+        if (prefs.contains(PREF_RESEARCH_LOGGER_UUID)) {
+            return prefs.getString(PREF_RESEARCH_LOGGER_UUID, null);
+        }
+        // Generate a random string as uuid if not yet set
+        final String newUuid = UUID.randomUUID().toString();
+        prefs.edit().putString(PREF_RESEARCH_LOGGER_UUID, newUuid).apply();
+        return newUuid;
+    }
+
+    public static boolean readResearchLoggerEnabledFlag(final SharedPreferences prefs) {
+        return prefs.getBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, false);
+    }
+
+    public static void writeResearchLoggerEnabledFlag(final SharedPreferences prefs,
+            final boolean isEnabled) {
+        prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, isEnabled).apply();
+    }
+
+    public static boolean readHasSeenSplash(final SharedPreferences prefs) {
+        return prefs.getBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, false);
+    }
+
+    public static void writeHasSeenSplash(final SharedPreferences prefs,
+            final boolean hasSeenSplash) {
+        prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, hasSeenSplash).apply();
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java
new file mode 100644
index 0000000..df495a8
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/Uploader.java
@@ -0,0 +1,180 @@
+/*
+ * 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.research;
+
+import android.Manifest;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.BatteryManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.inputmethod.latin.R;
+import com.android.inputmethod.latin.define.ProductionFlag;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Manages the uploading of ResearchLog files.
+ */
+public final class Uploader {
+    private static final String TAG = Uploader.class.getSimpleName();
+    private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
+    // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
+    private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
+            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;  // Force false for non-debug builds
+    private static final int BUF_SIZE = 1024 * 8;
+
+    private final Context mContext;
+    private final File mFilesDir;
+    private final URL mUrl;
+
+    public Uploader(final Context context) {
+        mContext = context;
+        mFilesDir = context.getFilesDir();
+
+        final String urlString = context.getString(R.string.research_logger_upload_url);
+        if (TextUtils.isEmpty(urlString)) {
+            mUrl = null;
+            return;
+        }
+        URL url = null;
+        try {
+            url = new URL(urlString);
+        } catch (final MalformedURLException e) {
+            Log.e(TAG, "Bad URL for uploading", e);
+        }
+        mUrl = url;
+    }
+
+    public boolean isPossibleToUpload() {
+        return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_AUTO_UPLOAD;
+    }
+
+    private boolean hasUploadingPermission() {
+        final PackageManager packageManager = mContext.getPackageManager();
+        return packageManager.checkPermission(Manifest.permission.INTERNET,
+                mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED;
+    }
+
+    public boolean isConvenientToUpload() {
+        return isExternallyPowered() && hasWifiConnection();
+    }
+
+    private boolean isExternallyPowered() {
+        final Intent intent = mContext.registerReceiver(null, new IntentFilter(
+                Intent.ACTION_BATTERY_CHANGED));
+        final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+        return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
+                || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
+    }
+
+    private boolean hasWifiConnection() {
+        final ConnectivityManager manager =
+                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
+        return wifiInfo.isConnected();
+    }
+
+    public void doUpload() {
+        if (mFilesDir == null) {
+            return;
+        }
+        final File[] files = mFilesDir.listFiles(new FileFilter() {
+            @Override
+            public boolean accept(final File pathname) {
+                return pathname.getName().startsWith(ResearchLogger.LOG_FILENAME_PREFIX)
+                        && !pathname.canWrite();
+            }
+        });
+        for (final File file : files) {
+            uploadFile(file);
+        }
+    }
+
+    private void uploadFile(final File file) {
+        if (DEBUG) {
+            Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
+        }
+        final int contentLength = (int) file.length();
+        HttpURLConnection connection = null;
+        InputStream fileInputStream = null;
+        try {
+            fileInputStream = new FileInputStream(file);
+            connection = (HttpURLConnection) mUrl.openConnection();
+            connection.setRequestMethod("PUT");
+            connection.setDoOutput(true);
+            connection.setFixedLengthStreamingMode(contentLength);
+            final OutputStream outputStream = connection.getOutputStream();
+            uploadContents(fileInputStream, outputStream);
+            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
+                Log.d(TAG, "upload failed: " + connection.getResponseCode());
+                final InputStream netInputStream = connection.getInputStream();
+                final BufferedReader reader = new BufferedReader(new InputStreamReader(
+                        netInputStream));
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    Log.d(TAG, "| " + reader.readLine());
+                }
+                reader.close();
+                return;
+            }
+            file.delete();
+            if (DEBUG) {
+                Log.d(TAG, "upload successful");
+            }
+        } catch (final IOException e) {
+            Log.e(TAG, "Exception uploading file", e);
+        } finally {
+            if (fileInputStream != null) {
+                try {
+                    fileInputStream.close();
+                } catch (final IOException e) {
+                    Log.e(TAG, "Exception closing uploaded file", e);
+                }
+            }
+            if (connection != null) {
+                connection.disconnect();
+            }
+        }
+    }
+
+    private static void uploadContents(final InputStream is, final OutputStream os)
+            throws IOException {
+        // TODO: Switch to NIO.
+        final byte[] buf = new byte[BUF_SIZE];
+        int numBytesRead;
+        while ((numBytesRead = is.read(buf)) != -1) {
+            os.write(buf, 0, numBytesRead);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java
index 89c67fb..26b6510 100644
--- a/java/src/com/android/inputmethod/research/UploaderService.java
+++ b/java/src/com/android/inputmethod/research/UploaderService.java
@@ -16,189 +16,44 @@
 
 package com.android.inputmethod.research;
 
-import android.Manifest;
 import android.app.AlarmManager;
 import android.app.IntentService;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.BatteryManager;
 import android.os.Bundle;
-import android.util.Log;
 
-import com.android.inputmethod.latin.R;
 import com.android.inputmethod.latin.define.ProductionFlag;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
-
+/**
+ * Service to invoke the uploader.
+ *
+ * Can be regularly invoked, invoked on boot, etc.
+ */
 public final class UploaderService extends IntentService {
     private static final String TAG = UploaderService.class.getSimpleName();
     private static final boolean DEBUG = false && ProductionFlag.IS_EXPERIMENTAL_DEBUG;
-    // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing
-    private static final boolean IS_INHIBITING_AUTO_UPLOAD = false
-            && ProductionFlag.IS_EXPERIMENTAL_DEBUG;  // Force false in production
     public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR;
     public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName()
             + ".extra.UPLOAD_UNCONDITIONALLY";
-    private static final int BUF_SIZE = 1024 * 8;
     protected static final int TIMEOUT_IN_MS = 1000 * 4;
 
-    private boolean mCanUpload;
-    private File mFilesDir;
-    private URL mUrl;
-
     public UploaderService() {
         super("Research Uploader Service");
     }
 
     @Override
-    public void onCreate() {
-        super.onCreate();
-
-        mCanUpload = false;
-        mFilesDir = null;
-        mUrl = null;
-
-        final PackageManager packageManager = getPackageManager();
-        final boolean hasPermission = packageManager.checkPermission(Manifest.permission.INTERNET,
-                getPackageName()) == PackageManager.PERMISSION_GRANTED;
-        if (!hasPermission) {
-            return;
-        }
-
-        try {
-            final String urlString = getString(R.string.research_logger_upload_url);
-            if (urlString == null || urlString.equals("")) {
-                return;
-            }
-            mFilesDir = getFilesDir();
-            mUrl = new URL(urlString);
-            mCanUpload = true;
-        } catch (MalformedURLException e) {
-            e.printStackTrace();
+    protected void onHandleIntent(final Intent intent) {
+        final Uploader uploader = new Uploader(this);
+        if (!uploader.isPossibleToUpload()) return;
+        if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) {
+            uploader.doUpload();
         }
     }
 
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        if (!mCanUpload) {
-            return;
+    private boolean isUploadingUnconditionally(final Bundle bundle) {
+        if (bundle == null) return false;
+        if (bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
+            return bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
         }
-        boolean isUploadingUnconditionally = false;
-        Bundle bundle = intent.getExtras();
-        if (bundle != null && bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) {
-            isUploadingUnconditionally = bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY);
-        }
-        doUpload(isUploadingUnconditionally);
-    }
-
-    private boolean isExternallyPowered() {
-        final Intent intent = registerReceiver(null, new IntentFilter(
-                Intent.ACTION_BATTERY_CHANGED));
-        final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
-        return pluggedState == BatteryManager.BATTERY_PLUGGED_AC
-                || pluggedState == BatteryManager.BATTERY_PLUGGED_USB;
-    }
-
-    private boolean hasWifiConnection() {
-        final ConnectivityManager manager =
-                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
-        final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
-        return wifiInfo.isConnected();
-    }
-
-    private void doUpload(final boolean isUploadingUnconditionally) {
-        if (!isUploadingUnconditionally && (!isExternallyPowered() || !hasWifiConnection()
-                || IS_INHIBITING_AUTO_UPLOAD)) {
-            return;
-        }
-        if (mFilesDir == null) {
-            return;
-        }
-        final File[] files = mFilesDir.listFiles(new FileFilter() {
-            @Override
-            public boolean accept(File pathname) {
-                return pathname.getName().startsWith(ResearchLogger.LOG_FILENAME_PREFIX)
-                        && !pathname.canWrite();
-            }
-        });
-        boolean success = true;
-        if (files.length == 0) {
-            success = false;
-        }
-        for (final File file : files) {
-            if (!uploadFile(file)) {
-                success = false;
-            }
-        }
-    }
-
-    private boolean uploadFile(File file) {
-        if (DEBUG) {
-            Log.d(TAG, "attempting upload of " + file.getAbsolutePath());
-        }
-        boolean success = false;
-        final int contentLength = (int) file.length();
-        HttpURLConnection connection = null;
-        InputStream fileInputStream = null;
-        try {
-            fileInputStream = new FileInputStream(file);
-            connection = (HttpURLConnection) mUrl.openConnection();
-            connection.setRequestMethod("PUT");
-            connection.setDoOutput(true);
-            connection.setFixedLengthStreamingMode(contentLength);
-            final OutputStream os = connection.getOutputStream();
-            final byte[] buf = new byte[BUF_SIZE];
-            int numBytesRead;
-            while ((numBytesRead = fileInputStream.read(buf)) != -1) {
-                os.write(buf, 0, numBytesRead);
-                if (DEBUG) {
-                    Log.d(TAG, new String(buf));
-                }
-            }
-            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
-                Log.d(TAG, "upload failed: " + connection.getResponseCode());
-                InputStream netInputStream = connection.getInputStream();
-                BufferedReader reader = new BufferedReader(new InputStreamReader(netInputStream));
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    Log.d(TAG, "| " + reader.readLine());
-                }
-                reader.close();
-                return success;
-            }
-            file.delete();
-            success = true;
-            if (DEBUG) {
-                Log.d(TAG, "upload successful");
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            if (fileInputStream != null) {
-                try {
-                    fileInputStream.close();
-                } catch (IOException e) {
-                    e.printStackTrace();
-                }
-            }
-            if (connection != null) {
-                connection.disconnect();
-            }
-        }
-        return success;
+        return false;
     }
 }
diff --git a/native/jni/src/char_utils.cpp b/native/jni/src/char_utils.cpp
index f1148f4..8d917ea 100644
--- a/native/jni/src/char_utils.cpp
+++ b/native/jni/src/char_utils.cpp
@@ -1070,12 +1070,14 @@
     /* 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, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F,
+    /* 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, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F,
+    /* 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,
diff --git a/native/jni/src/correction.cpp b/native/jni/src/correction.cpp
index d4bd4aa..0ae02d5 100644
--- a/native/jni/src/correction.cpp
+++ b/native/jni/src/correction.cpp
@@ -190,11 +190,11 @@
 }
 
 inline static bool isEquivalentChar(ProximityType type) {
-    return type == EQUIVALENT_CHAR;
+    return type == MATCH_CHAR;
 }
 
 inline static bool isProximityCharOrEquivalentChar(ProximityType type) {
-    return type == EQUIVALENT_CHAR || type == NEAR_PROXIMITY_CHAR;
+    return type == MATCH_CHAR || type == PROXIMITY_CHAR;
 }
 
 Correction::CorrectionType Correction::processCharAndCalcState(const int c, const bool isTerminal) {
@@ -214,14 +214,14 @@
         bool incremented = false;
         if (mLastCharExceeded && mInputIndex == mInputSize - 1) {
             // TODO: Do not check the proximity if EditDistance exceeds the threshold
-            const ProximityType matchId = mProximityInfoState.getMatchedProximityId(
+            const ProximityType matchId = mProximityInfoState.getProximityType(
                     mInputIndex, c, true, &proximityIndex);
             if (isEquivalentChar(matchId)) {
                 mLastCharExceeded = false;
                 --mExcessiveCount;
                 mDistances[mOutputIndex] =
                         mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
-            } else if (matchId == NEAR_PROXIMITY_CHAR) {
+            } else if (matchId == PROXIMITY_CHAR) {
                 mLastCharExceeded = false;
                 --mExcessiveCount;
                 ++mProximityCount;
@@ -268,7 +268,7 @@
 
     bool secondTransposing = false;
     if (mTransposedCount % 2 == 1) {
-        if (isEquivalentChar(mProximityInfoState.getMatchedProximityId(
+        if (isEquivalentChar(mProximityInfoState.getProximityType(
                 mInputIndex - 1, c, false))) {
             ++mTransposedCount;
             secondTransposing = true;
@@ -299,16 +299,16 @@
             : (noCorrectionsHappenedSoFar && mProximityCount == 0);
 
     ProximityType matchedProximityCharId = secondTransposing
-            ? EQUIVALENT_CHAR
-            : mProximityInfoState.getMatchedProximityId(
+            ? MATCH_CHAR
+            : mProximityInfoState.getProximityType(
                     mInputIndex, c, checkProximityChars, &proximityIndex);
 
-    if (UNRELATED_CHAR == matchedProximityCharId
+    if (SUBSTITUTION_CHAR == matchedProximityCharId
             || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         if (canTryCorrection && mOutputIndex > 0
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mExceeding
-                && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
+                && isEquivalentChar(mProximityInfoState.getProximityType(
                         mInputIndex, mWord[mOutputIndex - 1], false))) {
             if (DEBUG_CORRECTION
                     && (INPUTLENGTH_FOR_DEBUG <= 0 || INPUTLENGTH_FOR_DEBUG == mInputSize)
@@ -327,12 +327,12 @@
             // Here, we are doing something equivalent to matchedProximityCharId,
             // but we already know that "excessive char correction" just happened
             // so that we just need to check "mProximityCount == 0".
-            matchedProximityCharId = mProximityInfoState.getMatchedProximityId(
+            matchedProximityCharId = mProximityInfoState.getProximityType(
                     mInputIndex, c, mProximityCount == 0, &proximityIndex);
         }
     }
 
-    if (UNRELATED_CHAR == matchedProximityCharId
+    if (SUBSTITUTION_CHAR == matchedProximityCharId
             || ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
         if (ADDITIONAL_PROXIMITY_CHAR == matchedProximityCharId) {
             mAdditionalProximityMatching = true;
@@ -344,10 +344,10 @@
         if (mInputIndex < mInputSize - 1 && mOutputIndex > 0 && mTransposedCount > 0
                 && !mCorrectionStates[mOutputIndex].mTransposing
                 && mCorrectionStates[mOutputIndex - 1].mTransposing
-                && isEquivalentChar(mProximityInfoState.getMatchedProximityId(
+                && isEquivalentChar(mProximityInfoState.getProximityType(
                         mInputIndex, mWord[mOutputIndex - 1], false))
                 && isEquivalentChar(
-                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
             // Conversion t->e
             // Example:
             // occaisional -> occa   sional
@@ -359,7 +359,7 @@
                 && !mCorrectionStates[mOutputIndex].mTransposing
                 && mCorrectionStates[mOutputIndex - 1].mTransposing
                 && isEquivalentChar(
-                        mProximityInfoState.getMatchedProximityId(mInputIndex - 1, c, false))) {
+                        mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) {
             // Conversion t->s
             // Example:
             // chcolate -> chocolate
@@ -371,7 +371,7 @@
                 && mCorrectionStates[mOutputIndex].mProximityMatching
                 && mCorrectionStates[mOutputIndex].mSkipping
                 && isEquivalentChar(
-                        mProximityInfoState.getMatchedProximityId(mInputIndex - 1, c, false))) {
+                        mProximityInfoState.getProximityType(mInputIndex - 1, c, false))) {
             // Conversion p->s
             // Note: This logic tries saving cases like contrst --> contrast -- "a" is one of
             // proximity chars of "s", but it should rather be handled as a skipped char.
@@ -383,7 +383,7 @@
                 && mCorrectionStates[mOutputIndex].mSkipping
                 && mCorrectionStates[mOutputIndex].mAdditionalProximityMatching
                 && isProximityCharOrEquivalentChar(
-                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
             // Conversion s->a
             incrementInputIndex();
             --mSkippedCount;
@@ -392,7 +392,7 @@
             mDistances[mOutputIndex] = ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO;
         } else if ((mExceeding || mTransposing) && mInputIndex - 1 < mInputSize
                 && isEquivalentChar(
-                        mProximityInfoState.getMatchedProximityId(mInputIndex + 1, c, false))) {
+                        mProximityInfoState.getProximityType(mInputIndex + 1, c, false))) {
             // 1.2. Excessive or transpose correction
             if (mTransposing) {
                 ++mTransposedCount;
@@ -455,7 +455,7 @@
         mMatching = true;
         ++mEquivalentCharCount;
         mDistances[mOutputIndex] = mProximityInfoState.getNormalizedSquaredDistance(mInputIndex, 0);
-    } else if (NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
+    } else if (PROXIMITY_CHAR == matchedProximityCharId) {
         mProximityMatching = true;
         ++mProximityCount;
         mDistances[mOutputIndex] =
@@ -614,7 +614,7 @@
         multiplyIntCapped(matchWeight, &finalFreq);
     }
 
-    if (proximityInfoState->getMatchedProximityId(0, word[0], true) == UNRELATED_CHAR) {
+    if (proximityInfoState->getProximityType(0, word[0], true) == SUBSTITUTION_CHAR) {
         multiplyRate(FIRST_CHAR_DIFFERENT_DEMOTION_RATE, &finalFreq);
     }
 
diff --git a/native/jni/src/correction.h b/native/jni/src/correction.h
index 34f794d..f0d6210 100644
--- a/native/jni/src/correction.h
+++ b/native/jni/src/correction.h
@@ -119,7 +119,7 @@
     // proximity info state
     void initInputParams(const ProximityInfo *proximityInfo, const int *inputCodes,
             const int inputSize, const int *xCoordinates, const int *yCoordinates) {
-        mProximityInfoState.initInputParams(0, MAX_POINT_TO_KEY_LENGTH,
+        mProximityInfoState.initInputParams(0, static_cast<float>(MAX_VALUE_FOR_WEIGHTING),
                 proximityInfo, inputCodes, inputSize, xCoordinates, yCoordinates, 0, 0, false);
     }
 
diff --git a/native/jni/src/defines.h b/native/jni/src/defines.h
index a1fdae7..0aedc28 100644
--- a/native/jni/src/defines.h
+++ b/native/jni/src/defines.h
@@ -214,6 +214,8 @@
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
 
 #ifdef FLAG_FULL_DBG
 #define DEBUG_GEO_FULL true
@@ -237,6 +239,8 @@
 #define DEBUG_SAMPLING_POINTS false
 #define DEBUG_POINTS_PROBABILITY false
 #define DEBUG_DOUBLE_LETTER false
+#define DEBUG_CACHE false
+#define DEBUG_DUMP_ERROR false
 
 #define DEBUG_GEO_FULL false
 
@@ -271,7 +275,7 @@
 #define NOT_A_CODE_POINT (-1)
 #define NOT_A_DISTANCE (-1)
 #define NOT_A_COORDINATE (-1)
-#define EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO (-2)
+#define MATCH_CHAR_WITHOUT_DISTANCE_INFO (-2)
 #define PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO (-3)
 #define ADDITIONAL_PROXIMITY_CHAR_DISTANCE_INFO (-4)
 #define NOT_AN_INDEX (-1)
@@ -283,6 +287,7 @@
 
 #define CALIBRATE_SCORE_BY_TOUCH_COORDINATES true
 #define SUGGEST_MULTIPLE_WORDS true
+#define SUGGEST_INTERFACE_OUTPUT_SCALE 1000000.0f
 
 // The following "rate"s are used as a multiplier before dividing by 100, so they are in percent.
 #define WORDS_WITH_MISSING_CHARACTER_DEMOTION_RATE 80
@@ -336,8 +341,9 @@
 #define MAX_DEPTH_MULTIPLIER 3
 #define FIRST_WORD_INDEX 0
 
-// Max Distance between point to key
-#define MAX_POINT_TO_KEY_LENGTH 10000000
+// Max value for length, distance and probability which are used in weighting
+// TODO: Remove
+#define MAX_VALUE_FOR_WEIGHTING 10000000
 
 // The max number of the keys in one keyboard layout
 #define MAX_KEY_COUNT_IN_A_KEYBOARD 64
@@ -390,13 +396,15 @@
 // Used as a return value for character comparison
 typedef enum {
     // Same char, possibly with different case or accent
-    EQUIVALENT_CHAR,
+    MATCH_CHAR,
     // It is a char located nearby on the keyboard
-    NEAR_PROXIMITY_CHAR,
+    PROXIMITY_CHAR,
+    // Additional proximity char which can differ by language.
+    ADDITIONAL_PROXIMITY_CHAR,
+    // It is a substitution char
+    SUBSTITUTION_CHAR,
     // It is an unrelated char
     UNRELATED_CHAR,
-    // Additional proximity char which can differ by language.
-    ADDITIONAL_PROXIMITY_CHAR
 } ProximityType;
 
 typedef enum {
@@ -404,4 +412,19 @@
     A_DOUBLE_LETTER,
     A_STRONG_DOUBLE_LETTER
 } DoubleLetterLevel;
+
+typedef enum {
+    CT_MATCH,
+    CT_PROXIMITY,
+    CT_ADDITIONAL_PROXIMITY,
+    CT_SUBSTITUTION,
+    CT_OMISSION,
+    CT_INSERTION,
+    CT_TRANSPOSITION,
+    CT_SPACE_SUBSTITUTION,
+    CT_SPACE_OMISSION,
+    CT_COMPLETION,
+    CT_TERMINAL,
+    CT_NEW_WORD,
+} CorrectionType;
 #endif // LATINIME_DEFINES_H
diff --git a/native/jni/src/proximity_info.cpp b/native/jni/src/proximity_info.cpp
index 81eb0b3..74b5e01 100644
--- a/native/jni/src/proximity_info.cpp
+++ b/native/jni/src/proximity_info.cpp
@@ -204,6 +204,6 @@
     if (keyId0 >= 0 && keyId1 >= 0) {
         return mKeyKeyDistancesG[keyId0][keyId1];
     }
-    return MAX_POINT_TO_KEY_LENGTH;
+    return MAX_VALUE_FOR_WEIGHTING;
 }
 } // namespace latinime
diff --git a/native/jni/src/proximity_info.h b/native/jni/src/proximity_info.h
index 22bbdf1..57a175d 100644
--- a/native/jni/src/proximity_info.h
+++ b/native/jni/src/proximity_info.h
@@ -70,7 +70,7 @@
     int getKeyCenterYOfKeyIdG(int keyId) const;
     int getKeyKeyDistanceG(int keyId0, int keyId1) const;
 
-    void AK_FORCE_INLINE initializeProximities(const int *const inputCodes,
+    AK_FORCE_INLINE void initializeProximities(const int *const inputCodes,
             const int *const inputXCoordinates, const int *const inputYCoordinates,
             const int inputSize, int *allInputCodes) const {
         ProximityInfoUtils::initializeProximities(inputCodes, inputXCoordinates, inputYCoordinates,
@@ -79,10 +79,14 @@
                 KEY_COUNT, mLocaleStr, &mCodeToKeyMap, allInputCodes);
     }
 
-    int AK_FORCE_INLINE getKeyIndexOf(const int c) const {
+    AK_FORCE_INLINE int getKeyIndexOf(const int c) const {
         return ProximityInfoUtils::getKeyIndexOf(KEY_COUNT, c, &mCodeToKeyMap);
     }
 
+    AK_FORCE_INLINE bool isCodePointOnKeyboard(const int codePoint) const {
+        return getKeyIndexOf(codePoint) != NOT_AN_INDEX;
+    }
+
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(ProximityInfo);
 
diff --git a/native/jni/src/proximity_info_state.cpp b/native/jni/src/proximity_info_state.cpp
index bdbf8b1..fe1c433 100644
--- a/native/jni/src/proximity_info_state.cpp
+++ b/native/jni/src/proximity_info_state.cpp
@@ -36,6 +36,9 @@
     mIsContinuationPossible = ProximityInfoStateUtils::checkAndReturnIsContinuationPossible(
             inputSize, xCoordinates, yCoordinates, times, mSampledInputSize, &mSampledInputXs,
             &mSampledInputYs, &mSampledTimes, &mSampledInputIndice);
+    if (DEBUG_DICT) {
+        AKLOGI("isContinuationPossible = %s", (mIsContinuationPossible ? "true" : "false"));
+    }
 
     mProximityInfo = proximityInfo;
     mHasTouchPositionCorrectionData = proximityInfo->hasTouchPositionCorrectionData();
@@ -153,36 +156,26 @@
     }
 }
 
-// TODO: Remove the "scale" parameter
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
 // wrong to compare with mMaxPointToKeyLength.
 float ProximityInfoState::getPointToKeyLength(
-        const int inputIndex, const int codePoint, const float scale) const {
+        const int inputIndex, const int codePoint) const {
     const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
-        return min(mSampledDistanceCache_G[index] * scale, mMaxPointToKeyLength);
+        return min(mSampledDistanceCache_G[index], mMaxPointToKeyLength);
     }
     if (isSkippableCodePoint(codePoint)) {
         return 0.0f;
     }
     // If the char is not a key on the keyboard then return the max length.
-    return MAX_POINT_TO_KEY_LENGTH;
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
 }
 
-float ProximityInfoState::getPointToKeyLength_G(const int inputIndex, const int codePoint) const {
-    return getPointToKeyLength(inputIndex, codePoint, 1.0f);
-}
-
-// TODO: Remove the "scale" parameter
 float ProximityInfoState::getPointToKeyByIdLength(
-        const int inputIndex, const int keyId, const float scale) const {
+        const int inputIndex, const int keyId) const {
     return ProximityInfoStateUtils::getPointToKeyByIdLength(mMaxPointToKeyLength,
-            &mSampledDistanceCache_G, mProximityInfo->getKeyCount(), inputIndex, keyId, scale);
-}
-
-float ProximityInfoState::getPointToKeyByIdLength(const int inputIndex, const int keyId) const {
-    return getPointToKeyByIdLength(inputIndex, keyId, 1.0f);
+            &mSampledDistanceCache_G, mProximityInfo->getKeyCount(), inputIndex, keyId);
 }
 
 // In the following function, c is the current character of the dictionary word currently examined.
@@ -190,41 +183,42 @@
 // the same position. We want to see if c is in it: if so, then the word contains at that position
 // a character close to what the user typed.
 // What the user typed is actually the first character of the array.
-// proximityIndex is a pointer to the variable where getMatchedProximityId returns the index of c
+// proximityIndex is a pointer to the variable where getProximityType returns the index of c
 // in the proximity chars of the input index.
 // Notice : accented characters do not have a proximity list, so they are alone in their list. The
 // non-accented version of the character should be considered "close", but not the other keys close
 // to the non-accented version.
-ProximityType ProximityInfoState::getMatchedProximityId(const int index, const int c,
+ProximityType ProximityInfoState::getProximityType(const int index, const int codePoint,
         const bool checkProximityChars, int *proximityIndex) const {
     const int *currentCodePoints = getProximityCodePointsAt(index);
     const int firstCodePoint = currentCodePoints[0];
-    const int baseLowerC = toBaseLowerCase(c);
+    const int baseLowerC = toBaseLowerCase(codePoint);
 
     // The first char in the array is what user typed. If it matches right away, that means the
     // user typed that same char for this pos.
-    if (firstCodePoint == baseLowerC || firstCodePoint == c) {
-        return EQUIVALENT_CHAR;
+    if (firstCodePoint == baseLowerC || firstCodePoint == codePoint) {
+        return MATCH_CHAR;
     }
 
-    if (!checkProximityChars) return UNRELATED_CHAR;
+    if (!checkProximityChars) return SUBSTITUTION_CHAR;
 
     // If the non-accented, lowercased version of that first character matches c, then we have a
     // non-accented version of the accented character the user typed. Treat it as a close char.
     if (toBaseLowerCase(firstCodePoint) == baseLowerC) {
-        return NEAR_PROXIMITY_CHAR;
+        return PROXIMITY_CHAR;
     }
 
     // Not an exact nor an accent-alike match: search the list of close keys
     int j = 1;
     while (j < MAX_PROXIMITY_CHARS_SIZE
             && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
-        const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == c);
+        const bool matched = (currentCodePoints[j] == baseLowerC
+                || currentCodePoints[j] == codePoint);
         if (matched) {
             if (proximityIndex) {
                 *proximityIndex = j;
             }
-            return NEAR_PROXIMITY_CHAR;
+            return PROXIMITY_CHAR;
         }
         ++j;
     }
@@ -233,7 +227,8 @@
         ++j;
         while (j < MAX_PROXIMITY_CHARS_SIZE
                 && currentCodePoints[j] > ADDITIONAL_PROXIMITY_CHAR_DELIMITER_CODE) {
-            const bool matched = (currentCodePoints[j] == baseLowerC || currentCodePoints[j] == c);
+            const bool matched = (currentCodePoints[j] == baseLowerC
+                    || currentCodePoints[j] == codePoint);
             if (matched) {
                 if (proximityIndex) {
                     *proximityIndex = j;
@@ -243,7 +238,22 @@
             ++j;
         }
     }
-    // Was not included, signal this as an unrelated character.
+    // Was not included, signal this as a substitution character.
+    return SUBSTITUTION_CHAR;
+}
+
+ProximityType ProximityInfoState::getProximityTypeG(const int index, const int codePoint) const {
+    if (!isUsed()) {
+        return UNRELATED_CHAR;
+    }
+    const int lowerCodePoint = toLowerCase(codePoint);
+    const int baseLowerCodePoint = toBaseCodePoint(lowerCodePoint);
+    for (int i = 0; i < static_cast<int>(mSampledSearchKeyVectors[index].size()); ++i) {
+        if (mSampledSearchKeyVectors[index][i] == lowerCodePoint
+                || mSampledSearchKeyVectors[index][i] == baseLowerCodePoint) {
+            return MATCH_CHAR;
+        }
+    }
     return UNRELATED_CHAR;
 }
 
@@ -294,6 +304,6 @@
     if (it != mCharProbabilities[index].end()) {
         return it->second;
     }
-    return static_cast<float>(MAX_POINT_TO_KEY_LENGTH);
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
 }
 } // namespace latinime
diff --git a/native/jni/src/proximity_info_state.h b/native/jni/src/proximity_info_state.h
index c4cbd58..224240b 100644
--- a/native/jni/src/proximity_info_state.h
+++ b/native/jni/src/proximity_info_state.h
@@ -147,14 +147,14 @@
         return mIsContinuationPossible;
     }
 
-    float getPointToKeyByIdLength(const int inputIndex, const int keyId, const float scale) const;
     float getPointToKeyByIdLength(const int inputIndex, const int keyId) const;
-    float getPointToKeyLength(const int inputIndex, const int codePoint, const float scale) const;
-    float getPointToKeyLength_G(const int inputIndex, const int codePoint) const;
+    float getPointToKeyLength(const int inputIndex, const int codePoint) const;
 
-    ProximityType getMatchedProximityId(const int index, const int c,
+    ProximityType getProximityType(const int index, const int codePoint,
             const bool checkProximityChars, int *proximityIndex = 0) const;
 
+    ProximityType getProximityTypeG(const int index, const int codePoint) const;
+
     const std::vector<int> *getSearchKeyVector(const int index) const {
         return &mSampledSearchKeyVectors[index];
     }
diff --git a/native/jni/src/proximity_info_state_utils.cpp b/native/jni/src/proximity_info_state_utils.cpp
index 2bf327f..ccb28bc 100644
--- a/native/jni/src/proximity_info_state_utils.cpp
+++ b/native/jni/src/proximity_info_state_utils.cpp
@@ -209,7 +209,7 @@
                                 * ProximityInfoParams::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR);
             } else {
                 normalizedSquaredDistances[i * MAX_PROXIMITY_CHARS_SIZE + j] =
-                        (j == 0) ? EQUIVALENT_CHAR_WITHOUT_DISTANCE_INFO :
+                        (j == 0) ? MATCH_CHAR_WITHOUT_DISTANCE_INFO :
                                 PROXIMITY_CHAR_WITHOUT_DISTANCE_INFO;
             }
             if (DEBUG_PROXIMITY_CHARS) {
@@ -634,25 +634,17 @@
     return getAngleDiff(previousDirection, nextDirection);
 }
 
-// TODO: Remove the "scale" parameter
 // This function basically converts from a length to an edit distance. Accordingly, it's obviously
 // wrong to compare with mMaxPointToKeyLength.
 /* static */ float ProximityInfoStateUtils::getPointToKeyByIdLength(const float maxPointToKeyLength,
         const std::vector<float> *const SampledDistanceCache_G, const int keyCount,
-        const int inputIndex, const int keyId, const float scale) {
+        const int inputIndex, const int keyId) {
     if (keyId != NOT_AN_INDEX) {
         const int index = inputIndex * keyCount + keyId;
-        return min((*SampledDistanceCache_G)[index] * scale, maxPointToKeyLength);
+        return min((*SampledDistanceCache_G)[index], maxPointToKeyLength);
     }
     // If the char is not a key on the keyboard then return the max length.
-    return static_cast<float>(MAX_POINT_TO_KEY_LENGTH);
-}
-
-/* static */ float ProximityInfoStateUtils::getPointToKeyByIdLength(const float maxPointToKeyLength,
-        const std::vector<float> *const SampledDistanceCache_G, const int keyCount,
-        const int inputIndex, const int keyId) {
-    return getPointToKeyByIdLength(
-            maxPointToKeyLength, SampledDistanceCache_G, keyCount, inputIndex, keyId, 1.0f);
+    return static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
 }
 
 // Updates probabilities of aligning to some keys and skipping.
@@ -678,7 +670,7 @@
         const float currentAngle = getPointAngle(sampledInputXs, sampledInputYs, i);
         const float speedRate = (*sampledSpeedRates)[i];
 
-        float nearestKeyDistance = static_cast<float>(MAX_POINT_TO_KEY_LENGTH);
+        float nearestKeyDistance = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
         for (int j = 0; j < keyCount; ++j) {
             if ((*SampledNearKeySets)[i].test(j)) {
                 const float distance = getPointToKeyByIdLength(
@@ -1016,7 +1008,7 @@
     float sumLogProbability = 0.0f;
     // TODO: Current implementation is greedy algorithm. DP would be efficient for many cases.
     for (int i = 0; i < sampledInputSize && index < MAX_WORD_LENGTH - 1; ++i) {
-        float minLogProbability = static_cast<float>(MAX_POINT_TO_KEY_LENGTH);
+        float minLogProbability = static_cast<float>(MAX_VALUE_FOR_WEIGHTING);
         int character = NOT_AN_INDEX;
         for (hash_map_compat<int, float>::const_iterator it = (*charProbabilities)[i].begin();
                 it != (*charProbabilities)[i].end(); ++it) {
diff --git a/native/jni/src/proximity_info_state_utils.h b/native/jni/src/proximity_info_state_utils.h
index d55730a..a7f4a34 100644
--- a/native/jni/src/proximity_info_state_utils.h
+++ b/native/jni/src/proximity_info_state_utils.h
@@ -81,9 +81,6 @@
             std::vector<std::vector<int> > *sampledSearchKeyVectors);
     static float getPointToKeyByIdLength(const float maxPointToKeyLength,
             const std::vector<float> *const SampledDistanceCache_G, const int keyCount,
-            const int inputIndex, const int keyId, const float scale);
-    static float getPointToKeyByIdLength(const float maxPointToKeyLength,
-            const std::vector<float> *const SampledDistanceCache_G, const int keyCount,
             const int inputIndex, const int keyId);
     static void initGeometricDistanceInfos(const ProximityInfo *const proximityInfo,
             const int sampledInputSize, const int lastSavedInputSize,
diff --git a/native/jni/src/proximity_info_utils.h b/native/jni/src/proximity_info_utils.h
index 51cafba..71c97e3 100644
--- a/native/jni/src/proximity_info_utils.h
+++ b/native/jni/src/proximity_info_utils.h
@@ -226,7 +226,7 @@
             const int *const keyYCoordinates, const int *const keyWidths, const int *keyHeights,
             const int keyId, const int x, const int y) {
         // NOT_A_ID is -1, but return whenever < 0 just in case
-        if (keyId < 0) return MAX_POINT_TO_KEY_LENGTH;
+        if (keyId < 0) return MAX_VALUE_FOR_WEIGHTING;
         const int left = keyXCoordinates[keyId];
         const int top = keyYCoordinates[keyId];
         const int right = left + keyWidths[keyId];
diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
index 005f8b2..333b602 100644
--- a/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
+++ b/tests/src/com/android/inputmethod/latin/InputLogicTestsNonEnglish.java
@@ -74,15 +74,22 @@
     public void testWordThenSpaceDisplaysPredictions() {
         final String WORD_TO_TYPE = "beaujolais ";
         final String EXPECTED_RESULT = "nouveau";
-        changeLanguage("fr");
-        type(WORD_TO_TYPE);
-        sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
-        runMessages();
-        final SuggestionStripView suggestionStripView =
-                (SuggestionStripView)mInputView.findViewById(R.id.suggestion_strip_view);
-        final SuggestedWords suggestedWords = suggestionStripView.getSuggestions();
-        assertEquals("type word then type space yields predictions for French",
-                EXPECTED_RESULT, suggestedWords.getWord(0));
+        final boolean defaultNextWordPredictionOption =
+                mLatinIME.getResources().getBoolean(R.bool.config_default_next_word_prediction);
+        final boolean previousNextWordPredictionOption =
+                setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, true,
+                        defaultNextWordPredictionOption);
+        try {
+            changeLanguage("fr");
+            type(WORD_TO_TYPE);
+            sleep(DELAY_TO_WAIT_FOR_UNDERLINE);
+            runMessages();
+            assertEquals("type word then type space yields predictions for French",
+                    EXPECTED_RESULT, mLatinIME.getFirstSuggestedWord());
+        } finally {
+            setBooleanPreference(NEXT_WORD_PREDICTION_OPTION, previousNextWordPredictionOption,
+                    defaultNextWordPredictionOption);
+        }
     }
 
     public void testAutoCorrectForGerman() {
diff --git a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
index 1e20366..abfaf30 100644
--- a/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
+++ b/tests/src/com/android/inputmethod/latin/SubtypeLocaleTests.java
@@ -189,7 +189,7 @@
                         SubtypeLocale.getSubtypeDisplayName(FR_CA));
                 assertEquals("de   ", "Deutsch",
                         SubtypeLocale.getSubtypeDisplayName(DE));
-                assertEquals("zz   ", "Pas de langue (QWERTY)",
+                assertEquals("zz   ", "Aucune langue (QWERTY)",
                         SubtypeLocale.getSubtypeDisplayName(ZZ));
                 return null;
             }
@@ -301,7 +301,7 @@
     //  fr    azerty  F  Français
     //  fr_CA qwerty  F  Français (Canada)
     //  de    qwertz  F  Allemand
-    //  zz    qwerty  F  Pas de langue (QWERTY)
+    //  zz    qwerty  F  Aucune langue (QWERTY)
     //  fr    qwertz  T  Français (QWERTZ)
     //  de    qwerty  T  Allemand (QWERTY)
     //  en_US azerty  T  Anglais (États-Unis) (AZERTY)   exception
@@ -325,7 +325,7 @@
                         SubtypeLocale.getSubtypeDisplayNameInSystemLocale(FR_CA));
                 assertEquals("de   ", "Allemand",
                         SubtypeLocale.getSubtypeDisplayNameInSystemLocale(DE));
-                assertEquals("zz   ", "Pas de langue (QWERTY)",
+                assertEquals("zz   ", "Aucune langue (QWERTY)",
                         SubtypeLocale.getSubtypeDisplayNameInSystemLocale(ZZ));
                 return null;
             }
diff --git a/tools/maketext/res/values-ar/donottranslate-more-keys.xml b/tools/maketext/res/values-ar/donottranslate-more-keys.xml
index f10139e..cace240 100644
--- a/tools/maketext/res/values-ar/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-ar/donottranslate-more-keys.xml
@@ -99,31 +99,11 @@
          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
-         The following characters don't need BIDI mirroring.
-         U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
     <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
     <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-    <!-- U+00A2: "¢" CENT SIGN
-         U+00A3: "£" POUND SIGN
-         U+20AC: "€" EURO SIGN
-         U+00A5: "¥" YEN SIGN
-         U+20B1: "₱" PESO SIGN
-         U+FDFC: "﷼" RIAL SIGN -->
-    <string name="more_keys_for_currency_dollar">&#x00A2;,&#x00A3;,&#x20AC;,&#x00A5;,&#x20B1;,&#xFDFC;</string>
+    <string name="single_angle_quotes">!text/single_laqm_raqm_rtl</string>
+    <string name="double_angle_quotes">!text/double_laqm_raqm_rtl</string>
     <!-- U+0655: "ٕ" ARABIC HAMZA BELOW
          U+0654: "ٔ" ARABIC HAMZA ABOVE
          U+0652: "ْ" ARABIC SUKUN
diff --git a/tools/maketext/res/values-be/donottranslate-more-keys.xml b/tools/maketext/res/values-be/donottranslate-more-keys.xml
index c8f2167..4723503 100644
--- a/tools/maketext/res/values-be/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-be/donottranslate-more-keys.xml
@@ -37,4 +37,6 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-bg/donottranslate-more-keys.xml b/tools/maketext/res/values-bg/donottranslate-more-keys.xml
index e7570df..5262133 100644
--- a/tools/maketext/res/values-bg/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-bg/donottranslate-more-keys.xml
@@ -23,4 +23,6 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+    <!-- single_quotes of Bulgarian is default single_quotes_right_left. -->
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-cs/donottranslate-more-keys.xml b/tools/maketext/res/values-cs/donottranslate-more-keys.xml
index 9af6794..5ce1d3b 100644
--- a/tools/maketext/res/values-cs/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-cs/donottranslate-more-keys.xml
@@ -84,4 +84,8 @@
          U+017A: "ź" LATIN SMALL LETTER Z WITH ACUTE
          U+017C: "ż" LATIN SMALL LETTER Z WITH DOT ABOVE -->
     <string name="more_keys_for_z">&#x017E;,&#x017A;,&#x017C;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-da/donottranslate-more-keys.xml b/tools/maketext/res/values-da/donottranslate-more-keys.xml
index acc0c53..cbaf9f4 100644
--- a/tools/maketext/res/values-da/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-da/donottranslate-more-keys.xml
@@ -68,4 +68,8 @@
     <string name="more_keys_for_nordic_row2_10">&#x00E4;</string>
     <!-- U+00F6: "ö" LATIN SMALL LETTER O WITH DIAERESIS -->
     <string name="more_keys_for_nordic_row2_11">&#x00F6;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-de/donottranslate-more-keys.xml b/tools/maketext/res/values-de/donottranslate-more-keys.xml
index 181a3b5..9dc8717 100644
--- a/tools/maketext/res/values-de/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-de/donottranslate-more-keys.xml
@@ -55,4 +55,8 @@
     <!-- U+00F1: "ñ" LATIN SMALL LETTER N WITH TILDE
          U+0144: "ń" LATIN SMALL LETTER N WITH ACUTE -->
     <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-et/donottranslate-more-keys.xml b/tools/maketext/res/values-et/donottranslate-more-keys.xml
index 69cf654..d037044 100644
--- a/tools/maketext/res/values-et/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-et/donottranslate-more-keys.xml
@@ -111,4 +111,6 @@
     <string name="keylabel_for_nordic_row2_11">&#x00E4;</string>
     <!-- U+00F5: "õ" LATIN SMALL LETTER O WITH TILDE -->
     <string name="more_keys_for_nordic_row2_10">&#x00F5;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-fa/donottranslate-more-keys.xml b/tools/maketext/res/values-fa/donottranslate-more-keys.xml
index 6acdd41..7c8496d 100644
--- a/tools/maketext/res/values-fa/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-fa/donottranslate-more-keys.xml
@@ -106,33 +106,11 @@
          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
-         The following characters don't need BIDI mirroring.
-         U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
     <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&lt;|&gt;</string>
     <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&gt;|&lt;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,\",\'</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-    <!-- U+FDFC: "﷼" RIAL SIGN
-         U+060B: "؋" AFGHANI SIGN
-         U+00A2: "¢" CENT SIGN
-         U+00A3: "£" POUND SIGN
-         U+20AC: "€" EURO SIGN
-         U+00A5: "¥" YEN SIGN
-         U+20B1: "₱" PESO SIGN -->
-    <string name="keylabel_for_currency_generic">&#xFDFC;</string>
-    <string name="more_keys_for_currency_generic">$,&#x00A2;,&#x20AC;,&#x00A3;,&#x00A5;,&#x20B1;,&#x060B;</string>
+    <string name="single_angle_quotes">!text/single_laqm_raqm_rtl</string>
+    <string name="double_angle_quotes">!text/double_laqm_raqm_rtl</string>
     <!-- U+0655: "ٕ" ARABIC HAMZA BELOW
          U+0652: "ْ" ARABIC SUKUN
          U+0651: "ّ" ARABIC SHADDA
diff --git a/tools/maketext/res/values-hr/donottranslate-more-keys.xml b/tools/maketext/res/values-hr/donottranslate-more-keys.xml
index 9b4005d..022bd2a 100644
--- a/tools/maketext/res/values-hr/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-hr/donottranslate-more-keys.xml
@@ -35,4 +35,8 @@
     <string name="more_keys_for_c">&#x010D;,&#x0107;,&#x00E7;</string>
     <!-- U+0111: "đ" LATIN SMALL LETTER D WITH STROKE -->
     <string name="more_keys_for_d">&#x0111;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-hu/donottranslate-more-keys.xml b/tools/maketext/res/values-hu/donottranslate-more-keys.xml
index 4825910..ce2f5d0 100644
--- a/tools/maketext/res/values-hu/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-hu/donottranslate-more-keys.xml
@@ -59,4 +59,8 @@
          U+00F9: "ù" LATIN SMALL LETTER U WITH GRAVE
          U+016B: "ū" LATIN SMALL LETTER U WITH MACRON -->
     <string name="more_keys_for_u">&#x00FA;,&#x00FC;,&#x0171;,&#x00FB;,&#x00F9;,&#x016B;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-is/donottranslate-more-keys.xml b/tools/maketext/res/values-is/donottranslate-more-keys.xml
index 284aae9..4b4d986 100644
--- a/tools/maketext/res/values-is/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-is/donottranslate-more-keys.xml
@@ -70,4 +70,6 @@
     <string name="keylabel_for_nordic_row2_10">&#x00E6;</string>
     <!-- U+00FE: "þ" LATIN SMALL LETTER THORN -->
     <string name="keylabel_for_nordic_row2_11">&#x00FE;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-iw/donottranslate-more-keys.xml b/tools/maketext/res/values-iw/donottranslate-more-keys.xml
index 20b4c6a..64d4227 100644
--- a/tools/maketext/res/values-iw/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-iw/donottranslate-more-keys.xml
@@ -37,24 +37,18 @@
          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
-         The following characters don't need BIDI mirroring.
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
+    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
+    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
+    <!-- The following characters don't need BIDI mirroring.
          U+2018: "‘" LEFT SINGLE QUOTATION MARK
          U+2019: "’" RIGHT SINGLE QUOTATION MARK
          U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
          U+201C: "“" LEFT DOUBLE QUOTATION MARK
          U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;|&#x203A;,&#x2264;|&#x2265;,&#x00AB;|&#x00BB;</string>
-    <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;|&#x2039;,&#x2265;|&#x2264;,&#x00BB;|&#x00AB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_double_quote">&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;|&#x00BB;,&#x00BB|&#x00AB;;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-    <!-- U+20AA: "₪" NEW SHEQEL SIGN -->
-    <string name="keylabel_for_currency_generic">&#x20AA;</string>
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
+    <string name="single_quotes">&#x2018;,&#x2019;,&#x201A;</string>
+    <string name="double_quotes">&#x201C;,&#x201D;,&#x201E;</string>
+    <string name="single_angle_quotes">!text/single_laqm_raqm_rtl</string>
+    <string name="double_angle_quotes">!text/double_laqm_raqm_rtl</string>
 </resources>
diff --git a/tools/maketext/res/values-ka/donottranslate-more-keys.xml b/tools/maketext/res/values-ka/donottranslate-more-keys.xml
index c516c59..8c2add4 100644
--- a/tools/maketext/res/values-ka/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-ka/donottranslate-more-keys.xml
@@ -23,4 +23,6 @@
          U+10D1: "ბ" GEORGIAN LETTER BAN
          U+10D2: "გ" GEORGIAN LETTER GAN -->
     <string name="label_to_alpha_key">&#x10D0;&#x10D1;&#x10D2;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-lt/donottranslate-more-keys.xml b/tools/maketext/res/values-lt/donottranslate-more-keys.xml
index 1491d95..7e2b8a0 100644
--- a/tools/maketext/res/values-lt/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-lt/donottranslate-more-keys.xml
@@ -104,4 +104,6 @@
     <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
          U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
     <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-lv/donottranslate-more-keys.xml b/tools/maketext/res/values-lv/donottranslate-more-keys.xml
index d0a4448..c64e37b 100644
--- a/tools/maketext/res/values-lv/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-lv/donottranslate-more-keys.xml
@@ -103,4 +103,6 @@
     <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
          U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
     <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-mk/donottranslate-more-keys.xml b/tools/maketext/res/values-mk/donottranslate-more-keys.xml
index d0fa1e4..2db75c8 100644
--- a/tools/maketext/res/values-mk/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-mk/donottranslate-more-keys.xml
@@ -35,18 +35,6 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
-    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-nb/donottranslate-more-keys.xml b/tools/maketext/res/values-nb/donottranslate-more-keys.xml
index 49e6d5f..2cecb5e 100644
--- a/tools/maketext/res/values-nb/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-nb/donottranslate-more-keys.xml
@@ -57,4 +57,6 @@
     <string name="more_keys_for_nordic_row2_10">&#x00F6;</string>
     <!-- U+00E4: "ä" LATIN SMALL LETTER A WITH DIAERESIS -->
     <string name="more_keys_for_nordic_row2_11">&#x00E4;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
 </resources>
diff --git a/tools/maketext/res/values-nl/donottranslate-more-keys.xml b/tools/maketext/res/values-nl/donottranslate-more-keys.xml
index 73768af..e5d8295 100644
--- a/tools/maketext/res/values-nl/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-nl/donottranslate-more-keys.xml
@@ -63,4 +63,6 @@
     <string name="more_keys_for_n">&#x00F1;,&#x0144;</string>
     <!-- U+0133: "ij" LATIN SMALL LIGATURE IJ -->
     <string name="more_keys_for_y">&#x0133;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
 </resources>
diff --git a/tools/maketext/res/values-pl/donottranslate-more-keys.xml b/tools/maketext/res/values-pl/donottranslate-more-keys.xml
index 0f8a59b..b5cf6a0 100644
--- a/tools/maketext/res/values-pl/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-pl/donottranslate-more-keys.xml
@@ -62,4 +62,6 @@
     <string name="more_keys_for_z">&#x017C;,&#x017A;,&#x017E;</string>
     <!-- U+0142: "ł" LATIN SMALL LETTER L WITH STROKE -->
     <string name="more_keys_for_l">&#x0142;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
 </resources>
diff --git a/tools/maketext/res/values-ro/donottranslate-more-keys.xml b/tools/maketext/res/values-ro/donottranslate-more-keys.xml
index 44613cf..f399eb2 100644
--- a/tools/maketext/res/values-ro/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-ro/donottranslate-more-keys.xml
@@ -42,4 +42,6 @@
     <string name="more_keys_for_s">&#x0219;,&#x00DF;,&#x015B;,&#x0161;</string>
     <!-- U+021B: "ț" LATIN SMALL LETTER T WITH COMMA BELOW -->
     <string name="more_keys_for_t">&#x021B;</string>
+    <string name="single_quotes">!text/single_9qm_rqm</string>
+    <string name="double_quotes">!text/double_9qm_rqm</string>
 </resources>
diff --git a/tools/maketext/res/values-ru/donottranslate-more-keys.xml b/tools/maketext/res/values-ru/donottranslate-more-keys.xml
index bb47fec..f62c90f 100644
--- a/tools/maketext/res/values-ru/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-ru/donottranslate-more-keys.xml
@@ -37,4 +37,6 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values-sk/donottranslate-more-keys.xml b/tools/maketext/res/values-sk/donottranslate-more-keys.xml
index f6e1e8d..2ed538e 100644
--- a/tools/maketext/res/values-sk/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-sk/donottranslate-more-keys.xml
@@ -104,4 +104,8 @@
     <!-- U+0123: "ģ" LATIN SMALL LETTER G WITH CEDILLA
          U+011F: "ğ" LATIN SMALL LETTER G WITH BREVE -->
     <string name="more_keys_for_g">&#x0123;,&#x011F;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-sl/donottranslate-more-keys.xml b/tools/maketext/res/values-sl/donottranslate-more-keys.xml
index ccff2ac..1e5d1d7 100644
--- a/tools/maketext/res/values-sl/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-sl/donottranslate-more-keys.xml
@@ -27,4 +27,8 @@
     <string name="more_keys_for_d">&#x0111;</string>
     <!-- U+017E: "ž" LATIN SMALL LETTER Z WITH CARON -->
     <string name="more_keys_for_z">&#x017E;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-sr/donottranslate-more-keys.xml b/tools/maketext/res/values-sr/donottranslate-more-keys.xml
index c41ca95..c00d2a6 100644
--- a/tools/maketext/res/values-sr/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-sr/donottranslate-more-keys.xml
@@ -54,18 +54,8 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
-    <!-- U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201E;,&#x201C;,&#x201D;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!5,&#x201E;,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-sv/donottranslate-more-keys.xml b/tools/maketext/res/values-sv/donottranslate-more-keys.xml
index d479191..a36a13e 100644
--- a/tools/maketext/res/values-sv/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-sv/donottranslate-more-keys.xml
@@ -51,4 +51,6 @@
     <string name="more_keys_for_nordic_row2_10">&#x00F8;</string>
     <!-- U+00E6: "æ" LATIN SMALL LETTER AE -->
     <string name="more_keys_for_nordic_row2_11">&#x00E6;</string>
+    <string name="single_angle_quotes">!text/single_raqm_laqm</string>
+    <string name="double_angle_quotes">!text/double_raqm_laqm</string>
 </resources>
diff --git a/tools/maketext/res/values-uk/donottranslate-more-keys.xml b/tools/maketext/res/values-uk/donottranslate-more-keys.xml
index 09e246b..cc05cc6 100644
--- a/tools/maketext/res/values-uk/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values-uk/donottranslate-more-keys.xml
@@ -41,4 +41,6 @@
          U+0411: "Б" CYRILLIC CAPITAL LETTER BE
          U+0412: "В" CYRILLIC CAPITAL LETTER VE -->
     <string name="label_to_alpha_key">&#x0410;&#x0411;&#x0412;</string>
+    <string name="single_quotes">!text/single_9qm_lqm</string>
+    <string name="double_quotes">!text/double_9qm_lqm</string>
 </resources>
diff --git a/tools/maketext/res/values/donottranslate-more-keys.xml b/tools/maketext/res/values/donottranslate-more-keys.xml
index a5be348..c2b7513 100644
--- a/tools/maketext/res/values/donottranslate-more-keys.xml
+++ b/tools/maketext/res/values/donottranslate-more-keys.xml
@@ -62,13 +62,10 @@
     <string name="more_keys_for_cyrillic_i"></string>
     <!-- Label for "switch to alphabetic" key. -->
     <string name="label_to_alpha_key">ABC</string>
-    <string name="more_keys_for_single_quote">!fixedColumnOrder!4,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;</string> -->
-    <string name="more_keys_for_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;</string>
-    <!-- TODO: Neither DroidSans nor Roboto have the glyph for U+201F DOUBLE HIGH-REVERSED-9 QUOTATION MARK. -->
-    <!-- <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,&#x201C;,&#x201D;,&#x201E;,&#x201F;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string> -->
-    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!4,&#x201C;,&#x201D;,&#x00AB;,&#x00BB;,&#x2018;,&#x2019;,&#x201A;,&#x201B;</string>
+    <string name="single_quotes">!text/single_lqm_rqm</string>
+    <string name="double_quotes">!text/double_lqm_rqm</string>
+    <string name="single_angle_quotes">!text/single_laqm_raqm</string>
+    <string name="double_angle_quotes">!text/double_laqm_raqm</string>
     <!-- U+00A2: "¢" CENT SIGN
          U+00A3: "£" POUND SIGN
          U+20AC: "€" EURO SIGN
@@ -99,16 +96,7 @@
          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
-         The following characters don't need BIDI mirroring.
-         U+2018: "‘" LEFT SINGLE QUOTATION MARK
-         U+2019: "’" RIGHT SINGLE QUOTATION MARK
-         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
-         U+201B: "‛" SINGLE HIGH-REVERSED-9 QUOTATION MARK
-         U+201C: "“" LEFT DOUBLE QUOTATION MARK
-         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
-         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK
-         U+201F: "‟" DOUBLE HIGH-REVERSED-9 QUOTATION MARK -->
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
     <string name="more_keys_for_less_than">!fixedColumnOrder!3,&#x2039;,&#x2264;,&#x00AB;</string>
     <string name="more_keys_for_greater_than">!fixedColumnOrder!3,&#x203A;,&#x2265;,&#x00BB;</string>
     <string name="more_keys_for_arabic_diacritics"></string>
@@ -218,4 +206,43 @@
     <!-- popular web domains for the locale - most popular, displayed on the keyboard -->
     <string name="more_keys_for_popular_domain">"!hasLabels!,.net,.org,.gov,.edu"</string>
     <string name="more_keys_for_smiley">"!fixedColumnOrder!5,!hasLabels!,=-O|=-O ,:-P|:-P ,;-)|;-) ,:-(|:-( ,:-)|:-) ,:-!|:-! ,:-$|:-$ ,B-)|B-) ,:O|:O ,:-*|:-* ,:-D|:-D ,:\'(|:\'( ,:-\\\\|:-\\\\ ,O:-)|O:-) ,:-[|:-[ "</string>
+    <!-- U+2039: "‹" SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+         U+203A: "›" SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+         U+00AB: "«" LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+         U+00BB: "»" RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+         The following characters don't need BIDI mirroring.
+         U+2018: "‘" LEFT SINGLE QUOTATION MARK
+         U+2019: "’" RIGHT SINGLE QUOTATION MARK
+         U+201A: "‚" SINGLE LOW-9 QUOTATION MARK
+         U+201C: "“" LEFT DOUBLE QUOTATION MARK
+         U+201D: "”" RIGHT DOUBLE QUOTATION MARK
+         U+201E: "„" DOUBLE LOW-9 QUOTATION MARK -->
+    <!-- Abbreviations are:
+         laqm: LEFT-POINTING ANGLE QUOTATION MARK
+         raqm: RIGHT-POINTING ANGLE QUOTATION MARK
+         rtl: Right-To-Left script order
+         lqm: LEFT QUOTATION MARK
+         rqm: RIGHT QUOTATION MARK
+         9qm: LOW-9 QUOTATION MARK -->
+    <!--  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>. -->
+    <string name="single_laqm_raqm">&#x2039;,&#x203A;</string>
+    <string name="single_laqm_raqm_rtl">&#x2039;|&#x203A;,&#x203A;|&#x2039;</string>
+    <string name="single_raqm_laqm">&#x203A;,&#x2039;</string>
+    <string name="double_laqm_raqm">&#x00AB;,&#x00BB;</string>
+    <string name="double_laqm_raqm_rtl">&#x00AB;|&#x00BB;,&#x00BB;|&#x00AB;</string>
+    <string name="double_raqm_laqm">&#x00BB;,&#x00AB;</string>
+    <!-- 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>. -->
+    <string name="single_lqm_rqm">&#x201A;,&#x2018;,&#x2019;</string>
+    <string name="single_9qm_lqm">&#x2019;,&#x201A;,&#x2018;</string>
+    <string name="single_9qm_rqm">&#x2018;,&#x201A;,&#x2019;</string>
+    <string name="double_lqm_rqm">&#x201E;,&#x201C;,&#x201D;</string>
+    <string name="double_9qm_lqm">&#x201D;,&#x201E;,&#x201C;</string>
+    <string name="double_9qm_rqm">&#x201C;,&#x201E;,&#x201D;</string>
+    <string name="more_keys_for_single_quote">!fixedColumnOrder!5,!text/single_quotes,!text/single_angle_quotes</string>
+    <string name="more_keys_for_double_quote">!fixedColumnOrder!5,!text/double_quotes,!text/double_angle_quotes</string>
+    <string name="more_keys_for_tablet_double_quote">!fixedColumnOrder!6,!text/double_quotes,!text/single_quotes,!text/double_angle_quotes,!text/single_angle_quotes</string>
 </resources>